Диалоги исправления

На этой странице описывается, как решать проблемы, связанные с вердиктами о добросовестности.

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

Запросить диалог о целостности для исправления проблемы с вердиктом

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

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

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

    Котлин

    // 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())  

    Ява

    // 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());  

    Единство

    // 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" , отправьте клиенту приложения запрос на отображение диалогового окна лицензирования:

    Котлин

    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
    }

    Ява

    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;
    }

    Единство

    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 с запрошенным кодом, полученным с вашего сервера:

    Котлин

    // Show dialog as indicated by the server
    val showDialogType: Int? = yourServerResponse.integrityDialogTypeCode()
    if (showDialogType != null) {
      // Call showDialog with type code, the dialog will be shown on top of the
      // provided activity and complete when the dialog is closed.
      val integrityDialogResponseCode: Task<Int> =
      tokenResponse.showDialog(activity, showDialogType)
      // Handle response code, call the Integrity API again to confirm that
      // verdicts have been resolved.
    } 

    Ява

    // Show dialog as indicated by the server
    @Nullable Integer showDialogType = yourServerResponse.integrityDialogTypeCode();
    if (showDialogType != null) {
      // Call showDialog with type code, the dialog will be shown on top of the
      // provided activity and complete when the dialog is closed.
      Task<Integer> integrityDialogResponseCode =
          tokenResponse.showDialog(activity, showDialogType);
      // Handle response code, call the Integrity API again to confirm that
      // verdicts have been resolved.
    }

    Единство

    IEnumerator ShowDialogCoroutine() {
      int showDialogType = yourServerResponse.IntegrityDialogTypeCode();
    
      // Call showDialog with type code, the dialog will be shown on top of the
      // provided activity and complete when the dialog is closed.
      var showDialogTask = tokenResponse.ShowDialog(showDialogType);
    
      // Wait for PlayAsyncOperation to complete.
      yield return showDialogTask;
    
      // Handle response code, call the Integrity API again to confirm that
      // verdicts have been resolved.
    } 

    Unreal Engine

    // .h
    void MyClass::OnShowDialogCompleted(
      EStandardIntegrityErrorCode Error,
      EIntegrityDialogResponseCode Response)
    {
      // Handle response code, call the Integrity API again to confirm that
      // verdicts have 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) {
      /// Call showDialog with type code, the dialog will be shown on top of the
      /// provided activity and complete when the dialog is closed.
      StandardIntegrityErrorCode error_code =
          IntegrityTokenResponse_showDialog(response, activity, show_dialog_type);
    
      /// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
      if (error_code != STANDARD_INTEGRITY_NO_ERROR)
      {
          /// Remember to call the *_destroy() functions.
          return;
      }
    
      /// Use polling to wait for the async operation to complete.
      /// Note, the polling shouldn't block the thread where the IntegrityManager
      /// is running.
    
      IntegrityDialogResponseCode* response_code;
      error_code = StandardIntegrityToken_getDialogResponseCode(response, response_code);
    
      if (error_code != STANDARD_INTEGRITY_NO_ERROR)
      {
          /// Remember to call the *_destroy() functions.
          return;
      }
    
      /// Handle response code, call the Integrity API again to confirm that
      /// verdicts have been resolved.
    }
  5. Диалог отображается поверх предоставленного действия. Когда пользователь закрывает диалог, задача завершается с кодом ответа .

  6. (Необязательно) Запросите ещё один токен для отображения дальнейших диалоговых окон. При выполнении стандартных запросов необходимо повторно прогреть поставщика токена для получения нового вердикта.

Запросить диалог проверки целостности для исправления исключения на стороне клиента

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

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

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

    Котлин

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

    Ява

    private boolean isExceptionRemediable(ExecutionException exception) {
      Throwable cause = exception.getCause();
      if (cause instanceof StandardIntegrityException integrityException
    && integrityException.isRemediable()) {
          return true;
      }
      return false;
    }
     
  2. Если исключение устранимо, запросите диалог GET_INTEGRITY , используя возвращенное исключение. Диалог будет отображен поверх предоставленной активности, а возвращенная задача завершится с кодом ответа после того, как пользователь закроет диалог.

    Котлин

    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)
    }
     

    Ява

    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);
    }  
  3. Если возвращённый код ответа указывает на успешное выполнение, следующий запрос токена целостности должен быть выполнен без каких-либо исключений. При выполнении стандартных запросов необходимо повторно активировать поставщика токена для получения нового вердикта.

Коды диалога целостности

GET_LICENSED (код типа 1)

Вопрос о вердикте

Этот диалог уместен в двух случаях:

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

Ремедиация

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

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

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

Пример UX

Рисунок 1. Диалоговое окно воспроизведения GET_LICENSED.

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, сертифицированным Play Protect. Это может произойти, например, если загрузчик устройства разблокирован или загруженная ОС 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 .
  • Целостность приложения : если обнаружены такие проблемы, как несанкционированный доступ или несанкционированное изменение приложения, диалоговое окно предложит пользователям приобрести приложение из Play Store для их устранения.
  • Исключения на стороне клиента : диалоговое окно проверяет наличие и пытается устранить любые основные проблемы, вызвавшие исключение Integrity API. Например, оно может предложить пользователю обновить устаревшую версию сервисов Google Play.

Пример UX

Рисунок 4. Диалог GET_INTEGRITY. Поток устранения сетевых ошибок.

GET_STRONG_INTEGRITY (код типа 5)

Вопрос о вердикте

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

Ремедиация

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

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

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

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

Пример UX

Рисунок 5. Диалоговое окно GET_STRONG_INTEGRITY, обновляющее службы Play.