javaandroidauthorizationgoogle-signinandroid-credential-manager

When authorizing with the Android Authorization API, how can I prevent the permission confirmation popup from appearing every time?


I have successfully logged in with a Google account using CredentialManager and obtained a ServerAuthCode using the Authorization API.

However, there is a problem. Even though I use the same account every time and only request the same Scopes, a popup for user permission is displayed every time.

In other words, hasResolution() always returns true.

The code for the Authorization part I am using is as follows.

public class AuthorizationClient {
    static final int ACTIVITY_RESULT_IS_NOT_OK = 1;
    static final int EMPTY_SERVER_AUTH_CODE = 2;

    public static void authorize
        (AuthorizationListener listener, String clientId, boolean refreshToken, String[] scopes) {
        if (IntentRunnerActivity.isInitialized())
            authorizeInternal(listener, clientId, refreshToken, scopes);
        else
            IntentRunnerActivity.initialize(() -> authorizeInternal(listener, clientId, refreshToken, scopes));
    }

    static void authorizeInternal
        (AuthorizationListener listener, String clientId, boolean refreshToken, String[] scopes) {
        try {
            List<Scope> requestedScopes = new ArrayList<>();
            requestedScopes.add(new Scope("email"));
            if (scopes != null) for (String scope : scopes) requestedScopes.add(new Scope(scope));

            AuthorizationRequest authorizationRequest = new AuthorizationRequest.Builder()
                .requestOfflineAccess(clientId, refreshToken)
                .setRequestedScopes(requestedScopes)
                .build();

            Identity.getAuthorizationClient(UnityActivity.get())
                .authorize(authorizationRequest)
                .addOnSuccessListener(r -> {
                    if (r.hasResolution()) {
                        IntentRunnerActivity.run(r.getPendingIntent(), activityResult -> {
                            if (activityResult.getResultCode() == Activity.RESULT_OK) {
                                try {
                                    handleAuthorizationResult(listener,
                                        Identity.getAuthorizationClient(UnityActivity.get())
                                            .getAuthorizationResultFromIntent(activityResult.getData()));
                                } catch (ApiException e) {
                                    onException(listener, e);
                                }
                            } else listener.OnFail(ACTIVITY_RESULT_IS_NOT_OK);
                        });
                    } else handleAuthorizationResult(listener, r);
                })
                .addOnFailureListener(e -> onException(listener, e));
        } catch (Exception e) {
            onException(listener, e);
        }
    }

    static void handleAuthorizationResult(AuthorizationListener listener, AuthorizationResult result) {
        String serverAuthCode = result.getServerAuthCode();
        if (serverAuthCode != null) {
            listener.OnSuccess(serverAuthCode);
        } else listener.OnFail(EMPTY_SERVER_AUTH_CODE);
    }

    static void onException(AuthorizationListener listener, Exception e) {
        listener.OnException(e.toString());
    }
}
public class IntentRunnerActivity extends AppCompatActivity {
    static final AtomicReference<IntentRunnerInitializationListener> listener = new AtomicReference<>(null);
    static final AtomicReference<OnAuthorizationIntentResult> onResult = new AtomicReference<>(null);
    static IntentRunnerActivity instance;
    ActivityResultRegistry registry;
    ActivityResultLauncher<IntentSenderRequest> launcher;

    public static boolean isInitialized() {
        return listener.get() != null;
    }

    public static void initialize(IntentRunnerInitializationListener initializationListener) {
        listener.set(initializationListener);
        Context context = UnityContext.get();
        Intent intent = new Intent(context, IntentRunnerActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.registry = getActivityResultRegistry();
        this.launcher = registry.register(generateKey(), this,
            new ActivityResultContracts.StartIntentSenderForResult(), result -> {
                Intent intent = new Intent(this, UnityActivity.get().getClass());
                intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                startActivity(intent);
                onResult.get().OnResult(result);
            });
        instance = this;
        listener.get().OnCompleted();
    }

    public static void run(PendingIntent pendingIntent, OnAuthorizationIntentResult onResult) {
        IntentRunnerActivity.onResult.set(onResult);
        instance.launcher.launch(new IntentSenderRequest.Builder(pendingIntent).build());
    }

    private static String generateKey() {
        return UUID.randomUUID().toString();
    }

    public interface OnAuthorizationIntentResult {
        void OnResult(ActivityResult result);
    }
}
public class UnityActivity {
    static UnityActivity instance;
    static final Class<?> unityPlayerClass;
    final Field field;

    static {
        try {
            unityPlayerClass = Class.forName("com.unity3d.player.UnityPlayer");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public UnityActivity() throws NoSuchFieldException {
        this.field = unityPlayerClass.getDeclaredField("currentActivity");
        this.field.setAccessible(true);
    }

    static UnityActivity getInstance() throws NoSuchFieldException, ClassNotFoundException {
        if (instance != null) return instance;
        instance = new UnityActivity();
        return instance;
    }

    Activity getActivity() throws IllegalAccessException {
        return (Activity) field.get(null);
    }

    @NonNull
    public static Activity get() {
        try {
            return getInstance().getActivity();
        } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) {
            //noinspection DataFlowIssue
            return null;
        }
    }
}
public class UnityContext {
    private static final Context applicationContext = UnityActivity.get().getApplicationContext();

    public static Context get() {
        return applicationContext;
    }
}
    implementation 'com.google.android.gms:play-services-auth:21.2.0'
    implementation "androidx.credentials:credentials: 1.3.0-beta02"
    implementation "androidx.credentials:credentials-play-services-auth:1.3.0-beta02"
    implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"

    def appcompat_version = "1.6.1"
    implementation "androidx.appcompat:appcompat:$appcompat_version"
    implementation "androidx.appcompat:appcompat-resources:$appcompat_version"

How can I prevent the popup from appearing after the second time?


Solution

  • Resolved.

    new AuthorizationRequest.Builder()
        .requestOfflineAccess(clientId, refreshToken) // here
    

    I found that the consent screen is displayed every time when "true" is given to "forceCodeForRefreshToken" as an argument of the requestOfflineAccess function.

    https://developers.google.com/android/reference/com/google/android/gms/auth/api/identity/AuthorizationRequest.Builder

    According to the above explanation

    forceCodeForRefreshToken:
    If true, the granted code can be exchanged for an access token and a refresh token. 
    The first time you retrieve a code, a refresh token will be granted automatically. 
    Subsequent requests will require additional user consent. 
    Use false by default; 
    only use true if your server has suffered some failure and lost the user's refresh token.