Remediation dialogs

This page describes how to handle issues with integrity verdicts.

After an integrity token is requested, you have the option to display a Google Play dialog to the user. You may display the dialog when there is one or more issues with the integrity verdict or if an exception occurred during an Integrity API request. Once the dialog is closed, you can verify that the issue is fixed with another integrity token request. If you make standard requests, you need to warm up the token provider again to obtain a fresh verdict.

Request an integrity dialog to fix a verdict issue

When the client requests an integrity token, you can use the method offered in the StandardIntegrityToken (Standard API) and IntegrityTokenResponse (Classic API): showDialog(Activity activity, int integrityDialogTypeCode).

The following steps outline how you can use the Play Integrity API to show a remediation dialog using the GET_LICENSED dialog code. Other dialog codes that your app can request are listed after this section.

  1. Request an integrity token from your app, and send the token to your server. You can use the Standard or Classic request.

    Kotlin

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

    Java

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

    Unity

    // Request an integrity token
    StandardIntegrityTokentokenResponse=RequestIntegrityToken();
    // Send token to app server and get response on what to do next
    YourServerResponseyourServerResponse=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
    YourServerResponseYourServerResponse=SendToServer(Response->Token);

    Native

    /// Request an integrity token
    StandardIntegrityToken*response=requestIntegrityToken();
    /// Send token to app server and get response on what to do next
    YourServerResponseyourServerResponse=sendToServer(StandardIntegrityToken_getToken(response));
  2. On your server, decrypt the integrity token and check the appLicensingVerdict field. It could look something like this:

    // Licensing issue
    {
    ...
    "accountDetails":{
    "appLicensingVerdict":"UNLICENSED"
    }
    }
  3. If the token contains appLicensingVerdict: "UNLICENSED", reply to your app client, requesting it show the licensing dialog:

    Kotlin

    privatefungetDialogTypeCode(integrityToken:String):Int{
    // Get licensing verdict from decrypted and verified integritytoken
    vallicensingVerdict:String=getLicensingVerdictFromDecryptedToken(integrityToken)
    returnif(licensingVerdict=="UNLICENSED"){
    1// GET_LICENSED
    }else0
    }

    Java

    privateintgetDialogTypeCode(StringintegrityToken){
    // Get licensing verdict from decrypted and verified integrityToken
    StringlicensingVerdict=getLicensingVerdictFromDecryptedToken(integrityToken);
    if(licensingVerdict.equals("UNLICENSED")){
    return1;// GET_LICENSED
    }
    return0;
    }

    Unity

    privateintGetDialogTypeCode(stringIntegrityToken){
    // Get licensing verdict from decrypted and verified integrityToken
    stringlicensingVerdict=GetLicensingVerdictFromDecryptedToken(IntegrityToken);
    if(licensingVerdict=="UNLICENSED"){
    return1;// GET_LICENSED
    }
    return0;
    }

    Unreal Engine

    privateintGetDialogTypeCode(FStringIntegrityToken){
    // Get licensing verdict from decrypted and verified integrityToken
    FStringLicensingVerdict=GetLicensingVerdictFromDecryptedToken(IntegrityToken);
    if(LicensingVerdict=="UNLICENSED"){
    return1;// GET_LICENSED
    }
    return0;
    }

    Native

    privateintgetDialogTypeCode(stringintegrity_token){
    /// Get licensing verdict from decrypted and verified integrityToken
    stringlicensing_verdict=getLicensingVerdictFromDecryptedToken(integrity_token);
    if(licensing_verdict=="UNLICENSED"){
    return1;// GET_LICENSED
    }
    return0;
    }
  4. On your app, call showDialog with the requested code retrieved from your server:

    Kotlin

    // Show dialog as indicated by the server
    valshowDialogType:Int?=yourServerResponse.integrityDialogTypeCode()
    if(showDialogType==null){
    return
    }
    // Create dialog request
    valdialogRequest=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.
    valresult: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
    @NullableIntegershowDialogType=yourServerResponse.integrityDialogTypeCode();
    if(showDialogType==null){
    return;
    }
    // Create dialog request
    StandardIntegrityDialogRequestdialogRequest=
    StandardIntegrityDialogRequest.builder()
    .setActivity(getActivity())
    .setTypeCode(showDialogTypeCode)
    .setStandardIntegrityResponse(newStandardIntegrityResponse.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.
    varstandardIntegrityManager=newStandardIntegrityManagerV2();
    IEnumeratorShowDialogCoroutine(){
    intshowDialogType=yourServerResponse.IntegrityDialogTypeCode();
    PlayAsyncOperation<IntegrityDialogResponseCode,StandardIntegrityError>showDialogTask;
    using(varactivity=UnityPlayerHelper.GetCurrentActivity())
    {
    // Create a dialog request
    vardialogRequest=newStandardIntegrityDialogRequest(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.
    yieldreturnshowDialogTask;
    // Handle response code, call the Integrity API again to confirm that the
    // verdict issue been resolved.
    }

    Unreal Engine

    // .h
    voidMyClass::OnShowDialogCompleted(
    EStandardIntegrityErrorCodeError,
    EIntegrityDialogResponseCodeResponse)
    {
    // Handle response code, call the Integrity API again to confirm that the
    // verdict issue has been resolved.
    }
    // .cpp
    voidMyClass::RequestIntegrityToken()
    {
    UStandardIntegrityToken*Response=...
    intTypeCode=YourServerResponse.integrityDialogTypeCode();
    // Create a delegate to bind the callback function.
    FShowDialogStandardOperationCompletedDelegateDelegate;
    // 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);
    }

    Native

    // Show dialog as indicated by the server
    intshow_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;
    StandardIntegrityErrorCodeerror_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.
    IntegrityDialogResponseCoderesponse_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. The dialog is displayed on top of the provided activity. When the user has closed the dialog, the Task completes with a response code.

  6. (Optional) Request another token to display any further dialogs. If you make standard requests, you need to warm up the token provider again to obtain a fresh verdict.

Request an integrity dialog to fix a client side exception

If an Integrity API request fails with a StandardIntegrityException (Standard API) or IntegrityServiceException (Classic API) and the exception is remediable, you can use either the GET_INTEGRITY or GET_STRONG_INTEGRITY dialogs to fix the error.

The following steps outline how you can use the GET_INTEGRITY dialog to fix a remediable client side error reported by Integrity API.

  1. Check that the exception returned from an Integrity API request is remediable.

    Kotlin

    privatefunisExceptionRemediable(exception:ExecutionException):Boolean{
    valcause=exception.cause
    if(causeisStandardIntegrityException&&cause.isRemediable){
    returntrue
    }
    returnfalse
    }

    Java

    privatebooleanisExceptionRemediable(ExecutionExceptionexception){
    Throwablecause=exception.getCause();
    if(causeinstanceofStandardIntegrityExceptionintegrityException
    &&integrityException.isRemediable()){
    returntrue;
    }
    returnfalse;
    }

    Unity

    privateboolIsExceptionRemediable(StandardIntegrityErrorerror){
    // Check if the operation resulted in a remediable error
    if(error!=null&&error.ErrorCode!=StandardIntegrityErrorCode.NoError&&error.IsRemediable){
    returntrue;
    }
    returnfalse;
    }

    Native

    boolIsErrorRemediable(StandardIntegrityToken*token){
    /// Check if the error associated with the token is remediable
    boolisRemediable=false;
    if(StandardIntegrityToken_getIsRemediable(response,&isRemediable)==STANDARD_INTEGRITY_NO_ERROR){
    returnisRemediable;
    }
    returnfalse;
    }
  2. If the exception is remediable, request the GET_INTEGRITY dialog using the returned exception. The dialog will be displayed over the provided activity and the returned Task completes with a response code after the user closes the dialog.

    Kotlin

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

    Java

    privatevoidshowDialog(StandardIntegrityExceptionexception){
    // Create a dialog request
    StandardIntegrityDialogRequeststandardIntegrityDialogRequest=
    StandardIntegrityDialogRequest.builder()
    .setActivity(this.activity)
    .setType(IntegrityDialogTypeCode.GET_INTEGRITY)
    .setStandardIntegrityResponse(newExceptionDetails(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.
    varstandardIntegrityManager=newStandardIntegrityManagerV2();
    IEnumeratorShowDialogToFixError(StandardIntegrityErrorerror){
    PlayAsyncOperation<IntegrityDialogResponseCode,StandardIntegrityError>showDialogTask;
    using(varactivity=UnityPlayerHelper.GetCurrentActivity())
    {
    // Create a dialog request using the error
    vardialogRequest=newStandardIntegrityDialogRequest(error,activity,GET_INTEGRITY_DIALOG);
    // Request dialog
    showDialogTask=standardIntegrityManager.ShowDialog(dialogRequest);
    }
    // Wait for PlayAsyncOperation to complete.
    yieldreturnshowDialogTask;
    }

    Native

    privatevoidshowDialogToFixError(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;
    StandardIntegrityErrorCodeerror_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.
    IntegrityDialogResponseCoderesponse_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. If the returned response code indicates a success, the next request for an integrity token should succeed without any exceptions. If you make standard requests, you need to warm up the token provider again to obtain a fresh verdict.

Integrity dialog codes

GET_LICENSED (Type Code 1)

Verdict issue

This dialog is appropriate for two issues:

  • Unauthorized access: appLicensingVerdict: "UNLICENSED". This means the user account does not have an entitlement for your app, which can happen if the user sideloaded it or acquired it from a different app store than Google Play.
  • Tampered app: appRecognitionVerdict: "UNRECOGNIZED_VERSION". This means that your app's binary has been modified or is not a version recognized by Google Play.

Remediation

You can show the GET_LICENSED dialog to prompt the user to acquire the genuine app from Google Play. This single dialog addresses both scenarios:

  • For an unlicensed user, it grants them a Play license. This enables the user to receive app updates from Google Play.
  • For a user with a tampered app version, it guides them to installing the unmodified app from Google Play.

When the user completes the dialog, subsequent integrity checks return appLicensingVerdict: "LICENSED" and appRecognitionVerdict: "PLAY_RECOGNIZED".

Example UX

Figure 1. GET_LICENSED Play dialog.

CLOSE_UNKNOWN_ACCESS_RISK (Type Code 2)

Verdict issue

When environmentDetails.appAccessRiskVerdict.appsDetected contains "UNKNOWN_CAPTURING" or "UNKNOWN_CONTROLLING", it means there are other apps (not installed by Google Play or preloaded on the system partition by the device manufacturer) running on the device that could be capturing the screen or controlling the device.

Remediation

You can show the CLOSE_UNKNOWN_ACCESS_RISK dialog to prompt the user to close all unknown apps which could be capturing the screen or controlling the device. If the user taps the Close all button, all such apps are closed.

Example UX

Figure 2. Dialog for close unknown access risk.

CLOSE_ALL_ACCESS_RISK (Type Code 3)

Verdict issue

When environmentDetails.appAccessRiskVerdict.appsDetected contains any of "KNOWN_CAPTURING", "KNOWN_CONTROLLING","UNKNOWN_CAPTURING" or "UNKNOWN_CONTROLLING", it means there are apps running on the device that could be capturing the screen or controlling the device.

Remediation

You can show the CLOSE_ALL_ACCESS_RISK dialog to prompt the user to close all the apps which could be capturing the screen or controlling the device. If the user taps the Close all button, all such apps are closed on the device.

Example UX

Figure 3. Dialog for close all access risk.

GET_INTEGRITY (Type Code 4)

Verdict issue

This dialog is appropriate for any of the following issues:

  • Weak device integrity: When the deviceRecognitionVerdict does not contain MEETS_DEVICE_INTEGRITY, the device might not be a genuine and certified Android device. This can happen, for example, if the device's bootloader is unlocked or its loaded Android OS is not a certified manufacturer image.

  • Unauthorized access: appLicensingVerdict: "UNLICENSED". This means the user account does not have an entitlement for your app, which can happen if the user sideloaded it or acquired it from a different app store than Google Play.

  • Tampered app: appRecognitionVerdict: "UNRECOGNIZED_VERSION". This means that your app's binary has been modified or is not a version recognized by Google Play.

  • Client side exceptions: When a remediable exception occurs during an Integrity API request. Remediable exceptions are Integrity API exceptions with error codes like PLAY_SERVICES_VERSION_OUTDATED, NETWORK_ERROR, PLAY_SERVICES_NOT_FOUND, etc. You can use the exception.isRemediable() method to check if an exception is fixable by the dialog.

Remediation

The GET_INTEGRITY dialog is designed to streamline the user's experience by handling multiple remediation steps within a single, continuous flow. This prevents the user from having to interact with multiple separate dialogs to fix different issues.

When you request the dialog, it automatically detects which of the targeted verdict issues are present and provides the appropriate remediation steps. This means a single dialog request can address multiple problems at once, including:

  • Device integrity: If a device integrity issue is detected, the dialog will guide the user to improve the device's security status to meet the requirements for a MEETS_DEVICE_INTEGRITY verdict.
  • App integrity: If issues like unauthorized access or app tampering are detected, the dialog will direct users to acquire the app from the Play Store to fix them.
  • Client-side exceptions: The dialog checks for and attempts to resolve any underlying issues that caused an Integrity API exception. For example, it might prompt the user to update an outdated version of Google Play services.

Example UX

Figure 4. GET_INTEGRITY dialog network error remediation flow

GET_STRONG_INTEGRITY (Type Code 5)

Verdict issue

This dialog is designed to fix all of the same issues that GET_INTEGRITY addresses, with the added capability of fixing problems that prevent a device from receiving a MEETS_STRONG_INTEGRITY verdict and fixing Play Protect verdict issues.

Remediation

GET_STRONG_INTEGRITY is designed to streamline the user's experience by handling multiple remediation steps within a single, continuous flow. The dialog automatically checks for an address applicable integrity problems, including:

  • Device integrity: If a device integrity issue is detected, the dialog will guide the user to improve the device's security status to meet the requirements for a MEETS_STRONG_INTEGRITY verdict.
  • Play protect status: If the playProtectVerdict indicates an issue, the dialog will guide the user to fix it:

    • If Play Protect is disabled (playProtectVerdict == POSSIBLE_RISK), the dialog will prompt the user to enable it and perform a scan of all apps on the device.
    • If harmful apps are detected (playProtectVerdict == MEDIUM_RISK or HIGH_RISK), the dialog will direct the user to uninstall them using Google Play Protect.
  • App integrity: If issues like unauthorized access or app tampering are detected, the dialog will prompt the user to acquire the app from the Play Store to fix the problem.

  • Client-side exceptions: The dialog also attempts to resolve any underlying issues that caused an Integrity API exception. For example, it may prompt the user to enable Google Play services if it's found to be disabled. Remediable exceptions are Integrity API exceptions with error codes like PLAY_SERVICES_VERSION_OUTDATED, NETWORK_ERROR, or PLAY_SERVICES_NOT_FOUND. You can use the exception.isRemediable() method to check if an error is fixable by the dialog.

Example UX

Figure 5. GET_STRONG_INTEGRITY dialog updating Play services.

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年06月01日 UTC.