Диалоговые окна для устранения неполадок

На этой странице описано, как устранять проблемы, обнаруженные с помощью оценок целостности.

После запроса токена целостности вы можете вывести диалоговое окно Google Play, если оценка целостности указывает на наличие одной или нескольких проблем или если во время запроса к Integrity API возникло исключение. После закрытия окна вы можете отправить ещё один запрос токена целостности, чтобы проверить, была ли исправлена проблема. Если вы отправляете стандартные запросы, вам нужно снова прогреть поставщика токенов, чтобы получить новую оценку.

Как запросить диалоговое окно целостности, чтобы устранить проблему с оценкой

Когда клиент запрашивает токен целостности, вы можете использовать метод, предлагаемый в StandardIntegrityToken (Standard API) и IntegrityTokenResponse (Classic API): showDialog(Activity activity, int integrityDialogTypeCode).

Ниже описано, как с помощью Play Integrity API показывать диалоговое окно с кодом GET_LICENSED. Другие коды диалоговых окон, которые может запрашивать приложение, перечислены в следующем разделе.

  1. Запросите токен целостности из приложения и отправьте его на сервер. Вы можете использовать стандартный или классический запрос.

    Kotlin

    // Request an integrity token
    val tokenResponse: StandardIntegrityToken = requestIntegrityToken()
    // Send token to app server and get response on what to do next
    val yourServerResponse: YourServerResponse = sendToServer(tokenResponse.token())  

    Java

    // Request an integrity token
    StandardIntegrityToken tokenResponse = requestIntegrityToken();
    // Send token to app server and get response on what to do next
    YourServerResponse yourServerResponse = sendToServer(tokenResponse.token());  

    Unity

    // Request an integrity token
    StandardIntegrityToken tokenResponse = RequestIntegrityToken();
    // Send token to app server and get response on what to do next
    YourServerResponse yourServerResponse = sendToServer(tokenResponse.Token); 

    Unreal Engine

    // Request an integrity token
    StandardIntegrityToken* Response = RequestIntegrityToken();
    // Send token to app server and get response on what to do next
    YourServerResponse YourServerResponse = SendToServer(Response->Token); 

    Нативный код

    /// Request an integrity token
    StandardIntegrityToken* response = requestIntegrityToken();
    /// Send token to app server and get response on what to do next
    YourServerResponse yourServerResponse = sendToServer(StandardIntegrityToken_getToken(response));
  2. Расшифруйте токен целостности на сервере и проверьте поле appLicensingVerdict. Вот как это может выглядеть:

    // Licensing issue
    {
      ...
      "accountDetails": {
          "appLicensingVerdict": "UNLICENSED"
      }
    }
  3. Если токен содержит appLicensingVerdict: "UNLICENSED", отправьте клиенту приложения ответ с запросом на показ диалогового окна лицензирования:

    Kotlin

    private fun getDialogTypeCode(integrityToken: String): Int{
      // Get licensing verdict from decrypted and verified integritytoken
      val licensingVerdict: String = getLicensingVerdictFromDecryptedToken(integrityToken)
    
      return if (licensingVerdict == "UNLICENSED") {
              1 // GET_LICENSED
          } else 0
    }

    Java

    private int getDialogTypeCode(String integrityToken) {
      // Get licensing verdict from decrypted and verified integrityToken
      String licensingVerdict = getLicensingVerdictFromDecryptedToken(integrityToken);
    
      if (licensingVerdict.equals("UNLICENSED")) {
        return 1; // GET_LICENSED
      }
      return 0;
    }

    Unity

    private int GetDialogTypeCode(string IntegrityToken) {
      // Get licensing verdict from decrypted and verified integrityToken
      string licensingVerdict = GetLicensingVerdictFromDecryptedToken(IntegrityToken);
    
      if (licensingVerdict == "UNLICENSED") {
        return 1; // GET_LICENSED
      }
      return 0;
    } 

    Unreal Engine

    private int GetDialogTypeCode(FString IntegrityToken) {
      // Get licensing verdict from decrypted and verified integrityToken
      FString LicensingVerdict = GetLicensingVerdictFromDecryptedToken(IntegrityToken);
    
      if (LicensingVerdict == "UNLICENSED") {
        return 1; // GET_LICENSED
      }
      return 0;
    } 

    Нативный код

    private int getDialogTypeCode(string integrity_token) {
      /// Get licensing verdict from decrypted and verified integrityToken
      string licensing_verdict = getLicensingVerdictFromDecryptedToken(integrity_token);
    
      if (licensing_verdict == "UNLICENSED") {
        return 1; // GET_LICENSED
      }
      return 0;
    }
  4. В приложении вызовите showDialog с кодом, полученным от сервера:

    Kotlin

    // Show dialog as indicated by the server
    val showDialogType: Int? = yourServerResponse.integrityDialogTypeCode()
    if (showDialogType == null) {
       return
    }
    
    // Create dialog request
    val dialogRequest = StandardIntegrityDialogRequest.builder()
            .setActivity(activity)
            .setTypeCode(showDialogType)
            .setStandardIntegrityResponse(StandardIntegrityResponse.TokenResponse(token))
            .build()
    
    // Call showDialog, the dialog will be shown on top of the provided activity
    // and the task will complete when the dialog is closed.
    val result: Task<Int> = standardIntegrityManager.showDialog(dialogRequest)
    
    // Handle response code, call the Integrity API again to confirm that the
    // verdict issue has been resolved. 

    Java

    // Show dialog as indicated by the server
    @Nullable Integer showDialogType = yourServerResponse.integrityDialogTypeCode();
    if (showDialogType == null) {
       return;
    }
    
    // Create dialog request
    StandardIntegrityDialogRequest dialogRequest =
        StandardIntegrityDialogRequest.builder()
            .setActivity(getActivity())
            .setTypeCode(showDialogTypeCode)
            .setStandardIntegrityResponse(new StandardIntegrityResponse.TokenResponse(token))
            .build();
    
    // Call showDialog, the dialog will be shown on top of the provided activity
    // and the task will complete when the dialog is closed.
    Task<Integer> result = standardIntegrityManager.showDialog(dialogRequest);
    
    // Handle response code, call the Integrity API again to confirm that the
    // verdict issue has been resolved.

    Unity

    // Initialize the V2 manager. Requires Play Integrity Unity Plugin v2.0.0 or
    // higher.
    var standardIntegrityManager = new StandardIntegrityManagerV2();
    IEnumerator ShowDialogCoroutine() {
      int showDialogType = yourServerResponse.IntegrityDialogTypeCode();
    
      PlayAsyncOperation<IntegrityDialogResponseCode, StandardIntegrityError> showDialogTask;
    
      using (var activity = UnityPlayerHelper.GetCurrentActivity())
      {
          // Create a dialog request
          var dialogRequest = new StandardIntegrityDialogRequest(tokenResponse, activity, showDialogType);
    
          // Call showDialog with the request, the dialog will be shown on top of the
          // provided activity and complete when the dialog is closed.
          showDialogTask = standardIntegrityManager.ShowDialog(dialogRequest);
      }
    
      // Wait for PlayAsyncOperation to complete.
      yield return showDialogTask;
    
      // Handle response code, call the Integrity API again to confirm that the
      // verdict issue been resolved.
    } 

    Unreal Engine

    // .h
    void MyClass::OnShowDialogCompleted(
      EStandardIntegrityErrorCode Error,
      EIntegrityDialogResponseCode Response)
    {
      // Handle response code, call the Integrity API again to confirm that the
      // verdict issue has been resolved.
    }
    
    // .cpp
    void MyClass::RequestIntegrityToken()
    {
      UStandardIntegrityToken* Response = ...
      int TypeCode = YourServerResponse.integrityDialogTypeCode();
    
      // Create a delegate to bind the callback function.
      FShowDialogStandardOperationCompletedDelegate Delegate;
    
      // Bind the completion handler (OnShowDialogCompleted) to the delegate.
      Delegate.BindDynamic(this, &MyClass::OnShowDialogCompleted);
    
      // Call ShowDialog with TypeCode which completes when the dialog is closed.
      Response->ShowDialog(TypeCode, Delegate);
    }

    Нативный код

    // Show dialog as indicated by the server
    int show_dialog_type = yourServerResponse.integrityDialogTypeCode();
    if(show_dialog_type == 0){
    return;
    }
    
    /// Create dialog request
    StandardIntegrityDialogRequest* dialog_request;
    StandardIntegrityDialogRequest_create(&dialog_request);
    StandardIntegrityDialogRequest_setTypeCode(dialog_request, show_dialog_type);
    StandardIntegrityDialogRequest_setActivity(dialog_request, activity);
    StandardIntegrityDialogRequest_setStandardIntegrityToken(dialog_request,
                                                  token_response);
    
    /// Call showDialog with the dialog request. The dialog will be shown on top
    /// of the provided activity and complete when the dialog is closed by the
    /// user.
    StandardIntegrityDialogResponse* dialog_response;
    StandardIntegrityErrorCode error_code =
      StandardIntegrityManager_showDialog(dialog_request, &dialog_response);
    
    /// Use polling to wait for the async operation to complete. Note, the polling
    /// shouldn't block the thread where the StandardIntegrityManager is running.
    IntegrityDialogResponseCode response_code = INTEGRITY_DIALOG_RESPONSE_UNKNOWN;
    while (error_code == STANDARD_INTEGRITY_NO_ERROR) {
      error_code = StandardIntegrityDialogResponse_getResponseCode(dialog_response, &response_code);
      if(response_code != INTEGRITY_DIALOG_RESPONSE_UNKNOWN){
        break;
      }
    }
    
    /// Free memory
    StandardIntegrityDialogRequest_destroy(dialog_request);
    StandardIntegrityDialogResponse_destroy(dialog_response);
    
    /// Handle response code, call the Integrity API again to confirm that the
    /// verdict issues have been resolved.
  5. Диалоговое окно показывается поверх указанного действия. Когда пользователь закроет диалоговое окно, задача завершится с кодом ответа.

  6. При необходимости запросите ещё один токен, чтобы показать другие диалоговые окна. Если вы отправляете стандартные запросы, то для получения новой оценки вам нужно снова подготовить поставщика токенов.

Как запросить показ диалогового окна целостности, чтобы исправить исключение на стороне клиента

Если запрос Integrity API не удается выполнить и возвращается ошибка StandardIntegrityException (Standard API) или IntegrityServiceException (Classic API), которую можно устранить, используйте диалоговое окно GET_INTEGRITY или GET_STRONG_INTEGRITY.

Ниже описано, как использовать диалоговое окно GET_INTEGRITY, чтобы устранить ошибку на стороне клиента, о которой сообщает Integrity API.

  1. Убедитесь, что исключение, возвращенное из запроса к Integrity API, можно устранить.

    Kotlin

    private fun isExceptionRemediable(exception: ExecutionException): Boolean {
      val cause = exception.cause
      if (cause is StandardIntegrityException && cause.isRemediable) {
          return true
      }
      return false
    }

    Java

    private boolean isExceptionRemediable(ExecutionException exception) {
      Throwable cause = exception.getCause();
      if (cause instanceof StandardIntegrityException integrityException
    && integrityException.isRemediable()) {
          return true;
      }
      return false;
    }

    Unity

    private bool IsExceptionRemediable(StandardIntegrityError error) {
      // Check if the operation resulted in a remediable error
      if (error != null && error.ErrorCode != StandardIntegrityErrorCode.NoError && error.IsRemediable) {
          return true;
      }
      return false;
    }

    Нативный код

    bool IsErrorRemediable(StandardIntegrityToken* token) {
      /// Check if the error associated with the token is remediable
      bool isRemediable = false;
      if(StandardIntegrityToken_getIsRemediable(response, &isRemediable) == STANDARD_INTEGRITY_NO_ERROR){
        return isRemediable;
      }
      return false;
    }
  2. Если исключение можно устранить, запросите диалоговое окно GET_INTEGRITY, используя возвращенное исключение. Диалоговое окно будет показано поверх указанного действия, а возвращенная задача завершится с кодом ответа после того, как пользователь закроет диалоговое окно.

    Kotlin

    private fun showDialog(exception: StandardIntegrityException) {
      // Create a dialog request
      val standardIntegrityDialogRequest =
          StandardIntegrityDialogRequest.builder()
              .setActivity(activity)
              .setType(IntegrityDialogTypeCode.GET_INTEGRITY)
              .setStandardIntegrityResponse(ExceptionDetails(exception))
              .build()
    
      // Request dialog
      val responseCode: Task<Int> =
            standardIntegrityManager.showDialog(standardIntegrityDialogRequest)
    }
     

    Java

    private void showDialog(StandardIntegrityException exception) {
      // Create a dialog request
      StandardIntegrityDialogRequest standardIntegrityDialogRequest =
          StandardIntegrityDialogRequest.builder()
              .setActivity(this.activity)
              .setType(IntegrityDialogTypeCode.GET_INTEGRITY)
              .setStandardIntegrityResponse(new ExceptionDetails(exception))
              .build();
    
      // Request dialog
      Task<Integer> responseCode =
            standardIntegrityManager.showDialog(standardIntegrityDialogRequest);
    }  

    Unity

    // Initialize the V2 manager. Requires Play Integrity Unity Plugin v2.0.0 or
    // higher.
    var standardIntegrityManager = new StandardIntegrityManagerV2();
    IEnumerator ShowDialogToFixError(StandardIntegrityError error) {
      PlayAsyncOperation<IntegrityDialogResponseCode, StandardIntegrityError> showDialogTask;
    
      using (var activity = UnityPlayerHelper.GetCurrentActivity())
      {
          // Create a dialog request using the error
          var dialogRequest = new StandardIntegrityDialogRequest(error, activity, GET_INTEGRITY_DIALOG);
    
          // Request dialog
          showDialogTask = standardIntegrityManager.ShowDialog(dialogRequest);
      }
    
      // Wait for PlayAsyncOperation to complete.
      yield return showDialogTask;
    }

    Нативный код

    private void showDialogToFixError(StandardIntegrityToken* token) {
      /// If the token request failed, and the underlying error is not fixable
      /// then return early
      if(isErrorRemediable(token)) {
        return;
      }
    
      /// Create dialog request
      StandardIntegrityDialogRequest* dialog_request;
      StandardIntegrityDialogRequest_create(&dialog_request);
      StandardIntegrityDialogRequest_setTypeCode(dialog_request,
                                         kGetIntegrityDialogTypeCode);
      StandardIntegrityDialogRequest_setActivity(dialog_request, activity);
      StandardIntegrityDialogRequest_setStandardIntegrityToken(dialog_request,
                                                      token_response);
    
      /// Call showDialog with the dialog request. The dialog will be shown on
      /// top of the provided activity and complete when the dialog is closed by
      /// the user.
      StandardIntegrityDialogResponse* dialog_response;
      StandardIntegrityErrorCode error_code =
          StandardIntegrityManager_showDialog(dialog_request, &dialog_response);
    
      /// Use polling to wait for the async operation to complete.
      /// Note, the polling shouldn't block the thread where the
      /// StandardIntegrityManager is running.
      IntegrityDialogResponseCode response_code = INTEGRITY_DIALOG_RESPONSE_UNKNOWN;
      while (error_code == STANDARD_INTEGRITY_NO_ERROR) {
          error_code = StandardIntegrityDialogResponse_getResponseCode(response, &response_code);
          if(response_code != INTEGRITY_DIALOG_RESPONSE_UNKNOWN){
            break;
          }
      }
    
      /// Free memory
      StandardIntegrityDialogRequest_destroy(dialog_request);
      StandardIntegrityDialogResponse_destroy(dialog_response);
    
    }
  3. Если возвращенный код ответа указывает на успешное выполнение, следующий запрос токена целостности должен быть выполнен без исключений. Если вы отправляете стандартные запросы, вам нужно снова прогреть поставщика токенов, чтобы получить новую оценку.

Коды диалоговых окон проверки целостности

GET_LICENSED (код типа 1)

Проблема с оценкой

Это диалоговое окно подходит для двух проблем:

  • Несанкционированный доступ. appLicensingVerdict: "UNLICENSED". Это означает, что у аккаунта пользователя нет разрешения на использование вашего приложения. Такое может произойти, если пользователь установил его из неизвестного источника или приобрел в другом магазине приложений, отличном от Google Play.
  • Приложение, в которое были внесены изменения: appRecognitionVerdict: "UNRECOGNIZED_VERSION". Это означает, что исполняемый файл приложения был изменен или его версия не распознана Google Play.

Как устранить проблему

Вы можете показать диалоговое окно GET_LICENSED, чтобы предложить пользователю скачать подлинное приложение из Google Play. Это диалоговое окно предназначено для обоих сценариев:

  • Если у пользователя нет лицензии, он получит лицензию Google Play. Это позволяет пользователю получать обновления приложений из Google Play.
  • Если у пользователя установлена модифицированная версия приложения, ему будет предложено скачать оригинальную версию из Google Play.

Когда пользователь выполнит действия в диалоговом окне, последующие проверки целостности вернут значения appLicensingVerdict: "LICENSED" и appRecognitionVerdict: "PLAY_RECOGNIZED".

Пример UX

Рисунок 1. Диалоговое окно GET_LICENSED в Google Play.

CLOSE_UNKNOWN_ACCESS_RISK (код типа 2)

Проблема с оценкой

Если environmentDetails.appAccessRiskVerdict.appsDetected содержит "UNKNOWN_CAPTURING" или "UNKNOWN_CONTROLLING", это означает, что на устройстве запущены другие приложения, которые могут делать снимки экрана или управлять устройством. Такие приложения не были установлены через Google Play или предварительно загружены в системный раздел производителем устройства.

Как устранить проблему

Вы можете показать диалоговое окно CLOSE_UNKNOWN_ACCESS_RISK, чтобы предложить пользователю закрыть все неизвестные приложения, которые могут записывать экран или управлять устройством. Если пользователь нажмет кнопку Close all, все такие приложения будут закрыты.

Пример UX

Рисунок 2. Диалоговое окно для закрытия неизвестного риска доступа.

CLOSE_ALL_ACCESS_RISK (тип 3)

Проблема с оценкой

Если environmentDetails.appAccessRiskVerdict.appsDetected содержит "KNOWN_CAPTURING", "KNOWN_CONTROLLING", "UNKNOWN_CAPTURING" или "UNKNOWN_CONTROLLING", значит на устройстве запущены приложения, которые могут записывать экран или управлять устройством.

Как устранить проблему

Вы можете показать диалоговое окно CLOSE_ALL_ACCESS_RISK, чтобы предложить пользователю закрыть все приложения, которые могут записывать экран или управлять устройством. Если пользователь нажмет кнопку Close all, все такие приложения будут закрыты.

Пример UX

Рисунок 3. Диалоговое окно для закрытия всех рисков доступа.

GET_INTEGRITY (код типа 4)

Проблема с оценкой

Это диалоговое окно подходит для любой из следующих проблем:

  • Низкая целостность устройства. Если в deviceRecognitionVerdict нет MEETS_DEVICE_INTEGRITY, возможно, устройство не является подлинным и сертифицированным устройством Android. Это может произойти, например, если загрузчик операционной системы устройства разблокирован или загруженная ОС Android не является сертифицированным образом производителя.

  • Несанкционированный доступ. appLicensingVerdict: "UNLICENSED". Это означает, что у аккаунта пользователя нет разрешения на использование вашего приложения. Такое может произойти, если пользователь установил его из неизвестного источника или приобрел в другом магазине приложений, отличном от Google Play.

  • Приложение, в которое были внесены изменения: appRecognitionVerdict: "UNRECOGNIZED_VERSION". Это означает, что исполняемый файл приложения был изменен или его версия не распознана Google Play.

  • Исключения на стороне клиента. Возникают, когда во время запроса к Integrity API происходит устранимая ошибка. Устранимые исключения – это исключения Integrity API с кодами ошибок, такими как PLAY_SERVICES_VERSION_OUTDATED, NETWORK_ERROR, PLAY_SERVICES_NOT_FOUND и т. д. Чтобы проверить, можно ли устранить исключение с помощью диалогового окна, используйте метод exception.isRemediable().

Как устранить проблему

Диалоговое окно GET_INTEGRITY упрощает работу пользователя, позволяя выполнить несколько шагов по устранению неполадок в рамках одного непрерывного процесса. Это позволяет пользователю не взаимодействовать с несколькими отдельными диалоговыми окнами для устранения различных проблем.

При запросе диалогового окна автоматически определяется, какие из целевых проблем с оценкой присутствуют, и предлагаются соответствующие шаги по их устранению. Это означает, что один запрос в диалоге может решить сразу несколько проблем, в том числе:

  • Целостность устройства. Если обнаружена проблема с целостностью устройства, в диалоговом окне пользователю будет предложено повысить уровень безопасности устройства, чтобы оно соответствовало требованиям для оценки MEETS_DEVICE_INTEGRITY.
  • Целостность приложения. Если обнаружены проблемы, например несанкционированный доступ или вмешательство в работу приложения, пользователям будет предложено скачать приложение из Google Play.
  • Клиентские исключения. Диалоговое окно проверяет и пытается устранить любые основные проблемы, которые вызвали исключение Integrity API. Например, пользователю может быть предложено обновить устаревшую версию сервисов Google Play.

Пример UX

Рисунок 4. GET_INTEGRITY dialog network error remediation flow

GET_STRONG_INTEGRITY (код типа 5)

Проблема с оценкой

Это диалоговое окно предназначено для устранения всех проблем, которые решает GET_INTEGRITY, а также неполадок, из-за которых устройство не может получить оценку MEETS_STRONG_INTEGRITY и оценку Play Защиты.

Как устранить проблему

GET_STRONG_INTEGRITY позволяет упростить работу пользователя, выполняя несколько шагов по устранению неполадок в рамках одного непрерывного процесса. В диалоговом окне автоматически проверяются проблемы с целостностью адреса, в том числе:

  • Целостность устройства. Если обнаружена проблема с целостностью устройства, диалоговое окно поможет пользователю повысить уровень безопасности устройства, чтобы оно соответствовало требованиям для оценки MEETS_STRONG_INTEGRITY.
  • Статус Play Защиты. Если playProtectVerdict указывает на проблему, в диалоговом окне будут инструкции по ее устранению.

    • Если Play Защита отключена (playProtectVerdict == POSSIBLE_RISK), в диалоговом окне пользователю будет предложено включить ее и выполнить проверку всех приложений на устройстве.
    • Если обнаружены вредоносные приложения (playProtectVerdict == MEDIUM_RISK или HIGH_RISK), в диалоговом окне пользователю будет предложено удалить их с помощью Google Play Защиты.
  • Целостность приложения. Если обнаружены такие проблемы, как несанкционированный доступ или вмешательство в работу приложения, пользователю будет предложено скачать приложение из Google Play.

  • Клиентские исключения. Диалоговое окно также пытается устранить основные проблемы, которые вызвали исключение Integrity API. Например, если сервисы Google Play отключены, пользователю будет предложено включить их. К исключениям, которые можно устранить, относятся исключения Integrity API с кодами ошибок, например PLAY_SERVICES_VERSION_OUTDATED, NETWORK_ERROR или PLAY_SERVICES_NOT_FOUND. Чтобы проверить, можно ли устранить ошибку с помощью диалогового окна, используйте метод exception.isRemediable().

Пример UX

Рисунок 5. Диалоговое окно GET_STRONG_INTEGRITY, в котором обновляются сервисы Google Play.