Google PlayIntegrity API: a Nightmare

Thanks a lot @John_S for your answer, I’ll mark it as the final answer, anyway I post here all the missing parts for future developers so they can shortcut my almost one month sucked in this issue, as there is no complete documentation nor java examples (at the time of writing this) for the Google PlayIntegrity API.

First, you need to set our project in the Google Cloud, and Google Play as stated by @John_S, but the missing part is that you need to set a Credential as “Service Account” and then “Add Key” as described java.io.IOException: Error reading credentials from stream, ‘type’ field not specified and this https://developers.google.com/workspace/guides/create-credentials#android; then, you can download the .json file with your Credentials. The .json file described in my question is invalid as it must have a structure like this:

    {  "type": "service_account",
       "project_id": "your-project",
       "private_key_id": "your-key-id",
       "private_key": "your-private-key",
       "client_email": "[email protected]",
       "client_id": "your-client-id",
       "auth_uri": "https://accounts.google.com/o/oauth2/auth",
       "token_uri": "https://oauth2.googleapis.com/token",
       "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
       "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-email%40appspot.gserviceaccount.com"
    }

Second, once you have your valid .json file downloaded, store it in “src/main/resources/credentials.json” (create the new folder if needed, not into “res” folder), as stated here Where must the client_secrets.json file go in Android Studio project folder tree?

Third, to complete all the missing parts of the build.gradle you must include:

dependencies {
    implementation 'com.google.android.play:integrity:1.0.1'                  
    implementation 'com.google.apis:google-api-services-playintegrity:v1-rev20220211-1.32.1'
    implementation 'com.google.api-client:google-api-client-jackson2:1.20.0'
    implementation 'com.google.auth:google-auth-library-credentials:1.7.0'
    implementation 'com.google.auth:google-auth-library-oauth2-http:1.7.0'
}

And import them to your project

import com.google.android.gms.tasks.Task;
import com.google.android.play.core.integrity.IntegrityManager;
import com.google.android.play.core.integrity.IntegrityManagerFactory;
import com.google.android.play.core.integrity.IntegrityTokenRequest;
import com.google.android.play.core.integrity.IntegrityTokenResponse;
import com.google.api.services.playintegrity.v1.PlayIntegrity;
import com.google.api.services.playintegrity.v1.PlayIntegrityRequestInitializer;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.api.services.playintegrity.v1.model.DecodeIntegrityTokenRequest;
import com.google.api.services.playintegrity.v1.model.DecodeIntegrityTokenResponse;
import com.google.api.client.googleapis.services.GoogleClientRequestInitializer;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;

Then, the complete code for requesting the “Integrity Token” and decode it will be:

    // create the NONCE  Base64-encoded, URL-safe, and non-wrapped String
    String mynonce = Base64.encodeToString("this_is_my_nonce".getBytes(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);

    // Create an instance of a manager.
    IntegrityManager myIntegrityManager = IntegrityManagerFactory.create(getApplicationContext());

    // Request the integrity token by providing a nonce.
    Task<IntegrityTokenResponse> myIntegrityTokenResponse = myIntegrityManager
        .requestIntegrityToken(IntegrityTokenRequest
        .builder()
        .setNonce(mynonce)
//      .setCloudProjectNumber(cloudProjNumber)         // necessary only if sold outside Google Play
        .build());

        // get the time to check against the decoded integrity token time
        timeRequest = Calendar.getInstance().getTimeInMillis();

        myIntegrityTokenResponse.addOnSuccessListener(new OnSuccessListener<IntegrityTokenResponse>() {
            @Override
            public void onSuccess(IntegrityTokenResponse myIntegrityTokenResponse) {
                try {
                    String token = myIntegrityTokenResponse.token();

                    DecodeIntegrityTokenRequest requestObj = new DecodeIntegrityTokenRequest();
                    requestObj.setIntegrityToken(token);

                    //Configure your credentials from the downloaded Json file from the resource
                    GoogleCredentials credentials = GoogleCredentials.fromStream(Objects.requireNonNull(getClass().getClassLoader()).getResourceAsStream("credentials.json"));
                    HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);

                    HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
                    JsonFactory JSON_FACTORY  = new JacksonFactory();
                    GoogleClientRequestInitializer initializer = new PlayIntegrityRequestInitializer();

                    PlayIntegrity.Builder playIntegrity = new PlayIntegrity.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer).setApplicationName("your-project")
                        .setGoogleClientRequestInitializer(initializer);
                    PlayIntegrity play  = playIntegrity.build();

                    // the DecodeIntegrityToken must be run on a parallel thread
                    Thread thread = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try  {
                                DecodeIntegrityTokenResponse response = play.v1().decodeIntegrityToken("com.project.name", requestObj).execute();
                                String licensingVerdict = response.getTokenPayloadExternal().getAccountDetails().getAppLicensingVerdict();
                                if (licensingVerdict.equalsIgnoreCase("LICENSED")) {
                                    // Looks good! LICENSED app
                                } else {
                                    // LICENSE NOT OK
                                }
                            } catch (Exception e) {
                                //  LICENSE error
                            }
                        }
                    });

                    // execute the parallel thread 
                    thread.start();

                } catch (Error | IOException e) {
                    // LICENSE error
                } catch (Exception e) {
                    // LICENSE error
                }
            }
    });

Hope this helps.

Leave a Comment