From 6c162ff14ffce993ea839581d78f0015c47d56f2 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 25 Jun 2025 16:10:32 -0400 Subject: [PATCH] [google_sign_in] Use an activity for credential requests `getCredentialManager` is documented to require an activity context, not an application context; this fixes it to use the activity context since in some configurations using the application context will throw an error. Fixes https://github.com/flutter/flutter/issues/171122 --- .../google_sign_in_android/CHANGELOG.md | 5 ++ .../googlesignin/GoogleSignInPlugin.java | 13 +++- .../flutter/plugins/googlesignin/Messages.kt | 17 +++-- .../googlesignin/GoogleSignInTest.java | 64 +++++++++++++++---- .../lib/google_sign_in_android.dart | 2 + .../lib/src/messages.g.dart | 4 ++ .../pigeons/messages.dart | 4 ++ .../google_sign_in_android/pubspec.yaml | 2 +- 8 files changed, 91 insertions(+), 20 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 05954560607..1c810f7d3ac 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 7.0.1 + +* Passes an activity context when requesting credentials, fixing an issue that + prevented signing in on some devices. + ## 7.0.0 * **BREAKING CHANGE**: Switches to implementing version 3.0 of the platform diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java index edab543e932..5ff805d1308 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java @@ -219,6 +219,17 @@ public void getCredential( return; } + // getCredentialAsync requires an acitivity context, not an application context, per + // the API docs. + Activity activity = getActivity(); + if (activity == null) { + ResultUtilsKt.completeWithGetCredentialFailure( + callback, + new GetCredentialFailure( + GetCredentialFailureType.NO_ACTIVITY, "No activity available", null)); + return; + } + String nonce = params.getNonce(); GetCredentialRequest.Builder requestBuilder = new GetCredentialRequest.Builder(); if (params.getUseButtonFlow()) { @@ -243,7 +254,7 @@ public void getCredential( CredentialManager credentialManager = credentialManagerFactory.create(context); credentialManager.getCredentialAsync( - context, + activity, requestBuilder.build(), null, Executors.newSingleThreadExecutor(), diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/kotlin/io/flutter/plugins/googlesignin/Messages.kt b/packages/google_sign_in/google_sign_in_android/android/src/main/kotlin/io/flutter/plugins/googlesignin/Messages.kt index 783063d22f7..db70b631916 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/main/kotlin/io/flutter/plugins/googlesignin/Messages.kt +++ b/packages/google_sign_in/google_sign_in_android/android/src/main/kotlin/io/flutter/plugins/googlesignin/Messages.kt @@ -48,18 +48,23 @@ enum class GetCredentialFailureType(val raw: Int) { UNEXPECTED_CREDENTIAL_TYPE(0), /** Indicates that a server client ID was not provided. */ MISSING_SERVER_CLIENT_ID(1), + /** + * Indicates that the user needs to be prompted for authorization, but there is no current + * activity to prompt in. + */ + NO_ACTIVITY(2), /** The request was internally interrupted. */ - INTERRUPTED(2), + INTERRUPTED(3), /** The request was canceled by the user. */ - CANCELED(3), + CANCELED(4), /** No matching credential was found. */ - NO_CREDENTIAL(4), + NO_CREDENTIAL(5), /** The provider was not properly configured. */ - PROVIDER_CONFIGURATION_ISSUE(5), + PROVIDER_CONFIGURATION_ISSUE(6), /** The credential manager is not supported on this device. */ - UNSUPPORTED(6), + UNSUPPORTED(7), /** The request failed for an unknown reason. */ - UNKNOWN(7); + UNKNOWN(8); companion object { fun ofRaw(raw: Int): GetCredentialFailureType? { diff --git a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java index 88ac2f3c05e..ae3a05d83cb 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java @@ -191,6 +191,7 @@ public void getCredential_returnsAuthenticationInfo() { when(mockGoogleCredential.getIdToken()).thenReturn(idToken); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -214,7 +215,7 @@ public void getCredential_returnsAuthenticationInfo() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -233,6 +234,7 @@ public void getCredential_usesGetSignInWithGoogleOptionForButtonFlow() { "serverClientId", null); + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -245,7 +247,7 @@ public void getCredential_usesGetSignInWithGoogleOptionForButtonFlow() { ArgumentCaptor captor = ArgumentCaptor.forClass(GetCredentialRequest.class); verify(mockCredentialManager) - .getCredentialAsync(eq(mockContext), captor.capture(), any(), any(), any()); + .getCredentialAsync(eq(mockActivity), captor.capture(), any(), any(), any()); assertEquals(1, captor.getValue().getCredentialOptions().size()); assertTrue( @@ -261,6 +263,7 @@ public void getCredential_usesGetGoogleIdOptionForNonButtonFlow() { "serverClientId", null); + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -274,7 +277,7 @@ public void getCredential_usesGetGoogleIdOptionForNonButtonFlow() { ArgumentCaptor captor = ArgumentCaptor.forClass(GetCredentialRequest.class); verify(mockCredentialManager) - .getCredentialAsync(eq(mockContext), captor.capture(), any(), any(), any()); + .getCredentialAsync(eq(mockActivity), captor.capture(), any(), any(), any()); assertEquals(1, captor.getValue().getCredentialOptions().size()); assertTrue(captor.getValue().getCredentialOptions().get(0) instanceof GetGoogleIdOption); @@ -290,6 +293,7 @@ public void getCredential_passesNonceInButtonFlow() { "serverClientId", nonce); + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -303,7 +307,7 @@ public void getCredential_passesNonceInButtonFlow() { ArgumentCaptor captor = ArgumentCaptor.forClass(GetCredentialRequest.class); verify(mockCredentialManager) - .getCredentialAsync(eq(mockContext), captor.capture(), any(), any(), any()); + .getCredentialAsync(eq(mockActivity), captor.capture(), any(), any(), any()); assertEquals(1, captor.getValue().getCredentialOptions().size()); assertEquals( @@ -321,6 +325,7 @@ public void getCredential_passesNonceInNonButtonFlow() { "serverClientId", nonce); + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -334,13 +339,40 @@ public void getCredential_passesNonceInNonButtonFlow() { ArgumentCaptor captor = ArgumentCaptor.forClass(GetCredentialRequest.class); verify(mockCredentialManager) - .getCredentialAsync(eq(mockContext), captor.capture(), any(), any(), any()); + .getCredentialAsync(eq(mockActivity), captor.capture(), any(), any(), any()); assertEquals(1, captor.getValue().getCredentialOptions().size()); assertEquals( nonce, ((GetGoogleIdOption) captor.getValue().getCredentialOptions().get(0)).getNonce()); } + @Test + public void getCredential_reportsMissingActivity() { + GetCredentialRequestParams params = + new GetCredentialRequestParams( + false, + new GetCredentialRequestGoogleIdOptionParams(false, false), + "serverClientId", + null); + + final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(null); + plugin.getCredential( + params, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + // This failure is a structured return value, not an exception. + assertTrue(reply.isSuccess()); + GetCredentialResult result = reply.getOrNull(); + assertTrue(result instanceof GetCredentialFailure); + GetCredentialFailure failure = (GetCredentialFailure) result; + assertEquals(GetCredentialFailureType.NO_ACTIVITY, failure.getType()); + return null; + })); + assertTrue(callbackCalled[0]); + } + @Test public void getCredential_reportsMissingServerClientId() { GetCredentialRequestParams params = @@ -348,6 +380,7 @@ public void getCredential_reportsMissingServerClientId() { false, new GetCredentialRequestGoogleIdOptionParams(false, false), null, null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -374,6 +407,7 @@ public void getCredential_reportsWrongCredentialType() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -393,7 +427,7 @@ public void getCredential_reportsWrongCredentialType() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -417,6 +451,7 @@ public void getCredential_reportsCancellation() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -436,7 +471,7 @@ public void getCredential_reportsCancellation() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -456,6 +491,7 @@ public void getCredential_reportsInterrupted() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -475,7 +511,7 @@ public void getCredential_reportsInterrupted() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -495,6 +531,7 @@ public void getCredential_reportsProviderConfigurationIssue() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -515,7 +552,7 @@ public void getCredential_reportsProviderConfigurationIssue() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -535,6 +572,7 @@ public void getCredential_reportsUnsupported() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -554,7 +592,7 @@ public void getCredential_reportsUnsupported() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -574,6 +612,7 @@ public void getCredential_reportsNoCredential() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -593,7 +632,7 @@ public void getCredential_reportsNoCredential() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), @@ -613,6 +652,7 @@ public void getCredential_reportsUnknown() { null); final Boolean[] callbackCalled = new Boolean[1]; + plugin.setActivity(mockActivity); plugin.getCredential( params, ResultCompat.asCompatCallback( @@ -632,7 +672,7 @@ public void getCredential_reportsUnknown() { callbackCaptor = ArgumentCaptor.forClass(CredentialManagerCallback.class); verify(mockCredentialManager) .getCredentialAsync( - eq(mockContext), + eq(mockActivity), any(GetCredentialRequest.class), any(), any(), diff --git a/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart index f22e58dac86..3481f4cf0e5 100644 --- a/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart +++ b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart @@ -164,6 +164,8 @@ class GoogleSignInAndroid extends GoogleSignInPlatform { // distinct code. code = GoogleSignInExceptionCode.providerConfigurationError; message = 'Unexpected credential type: $message'; + case GetCredentialFailureType.noActivity: + code = GoogleSignInExceptionCode.uiUnavailable; case GetCredentialFailureType.interrupted: code = GoogleSignInExceptionCode.interrupted; case GetCredentialFailureType.providerConfigurationIssue: diff --git a/packages/google_sign_in/google_sign_in_android/lib/src/messages.g.dart b/packages/google_sign_in/google_sign_in_android/lib/src/messages.g.dart index 240e2a56b11..fa5f335ddbe 100644 --- a/packages/google_sign_in/google_sign_in_android/lib/src/messages.g.dart +++ b/packages/google_sign_in/google_sign_in_android/lib/src/messages.g.dart @@ -26,6 +26,10 @@ enum GetCredentialFailureType { /// Indicates that a server client ID was not provided. missingServerClientId, + /// Indicates that the user needs to be prompted for authorization, but there + /// is no current activity to prompt in. + noActivity, + /// The request was internally interrupted. interrupted, diff --git a/packages/google_sign_in/google_sign_in_android/pigeons/messages.dart b/packages/google_sign_in/google_sign_in_android/pigeons/messages.dart index b7cdd267a7e..c142b097d6a 100644 --- a/packages/google_sign_in/google_sign_in_android/pigeons/messages.dart +++ b/packages/google_sign_in/google_sign_in_android/pigeons/messages.dart @@ -81,6 +81,10 @@ enum GetCredentialFailureType { /// Indicates that a server client ID was not provided. missingServerClientId, + /// Indicates that the user needs to be prompted for authorization, but there + /// is no current activity to prompt in. + noActivity, + // Types from https://developer.android.com/reference/android/credentials/GetCredentialException /// The request was internally interrupted. interrupted, diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 2ddf590bbcb..c9ab108fc5d 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 7.0.0 +version: 7.0.1 environment: sdk: ^3.6.0