This page describes how to handle issues with integrity verdicts.
When 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. The dialog is displayed on top of your app, and prompts users to resolve the cause of the issue. Once the dialog is closed, you can verify that the issue is fixed with another request to the Integrity API.
Integrity dialogs
GET_LICENSED (Type Code 1)
Verdict issue
When appLicensingVerdict == "UNLICENSED"
. This means the user account is
unlicensed. In other words, they didn't install or buy the app from Google Play.
Remediation
You can show the GET_LICENSED
dialog to prompt the user to get your app from
Google Play. If the user accepts, the user account becomes licensed
(appLicensingVerdict == "LICENSED"
). The app is added to the user's Google
Play library and Google Play can deliver app updates on your behalf.
Example UX
CLOSE_UNKNOWN_ACCESS_RISK (Type Code 2)
Verdict issue
When environmentDetails.appAccessRiskVerdict.appsDetected
contains
"UNKNOWN_CAPTURING"
or "UNKNOWN_CONTROLLING"
, it means there are unknown
apps 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
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
Request an integrity dialog
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 the GET_LICENSED dialog:
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 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);
Native
/// 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));
On your server, decrypt the integrity token and check the
appLicensingVerdict
field. It could look something like this:// Licensing issue { ... accountDetails: { appLicensingVerdict: "UNLICENSED" } }
If the token contains
appLicensingVerdict: "UNLICENSED"
, reply to your app client, requesting it show the licensing dialog: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; }
Native
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; }
On your app, call
showDialog
with the requested code retrieved from your server:Kotlin
// 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. }
Java
// 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. }
Unity
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. }
Native
// 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. }
The dialog is displayed on top of the provided activity. When the user has closed the dialog, the Task completes with a response code.
(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.