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?
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.
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.