99import android .app .Activity ;
1010import android .content .Context ;
1111import android .content .Intent ;
12+ import android .os .Handler ;
13+ import android .os .Looper ;
1214import android .util .Log ;
1315import androidx .annotation .NonNull ;
1416import androidx .annotation .Nullable ;
2527import com .google .android .gms .common .api .Scope ;
2628import com .google .android .gms .tasks .RuntimeExecutionException ;
2729import com .google .android .gms .tasks .Task ;
28- import com .google .common .base .Joiner ;
29- import com .google .common .base .Strings ;
3030import io .flutter .embedding .engine .plugins .FlutterPlugin ;
3131import io .flutter .embedding .engine .plugins .activity .ActivityAware ;
3232import io .flutter .embedding .engine .plugins .activity .ActivityPluginBinding ;
3737import java .util .ArrayList ;
3838import java .util .List ;
3939import java .util .Objects ;
40- import java .util .concurrent .Callable ;
41- import java .util .concurrent .ExecutionException ;
4240
4341/** Google sign-in plugin for Flutter. */
4442public class GoogleSignInPlugin implements FlutterPlugin , ActivityAware {
@@ -135,8 +133,6 @@ public static class Delegate implements PluginRegistry.ActivityResultListener, G
135133 private final @ NonNull Context context ;
136134 // Only set activity for v2 embedder. Always access activity from getActivity() method.
137135 private @ Nullable Activity activity ;
138- // TODO(stuartmorgan): See whether this can be replaced with background channels.
139- private final BackgroundTaskRunner backgroundTaskRunner = new BackgroundTaskRunner (1 );
140136 private final GoogleSignInWrapper googleSignInWrapper ;
141137
142138 private GoogleSignInClient signInClient ;
@@ -224,15 +220,15 @@ public void init(@NonNull Messages.InitParams params) {
224220 // https://developers.google.com/android/guides/client-auth
225221 // https://developers.google.com/identity/sign-in/android/start#configure-a-google-api-project
226222 String serverClientId = params .getServerClientId ();
227- if (!Strings . isNullOrEmpty (params .getClientId ()) && Strings . isNullOrEmpty (serverClientId )) {
223+ if (!isNullOrEmpty (params .getClientId ()) && isNullOrEmpty (serverClientId )) {
228224 Log .w (
229225 "google_sign_in" ,
230226 "clientId is not supported on Android and is interpreted as serverClientId. "
231227 + "Use serverClientId instead to suppress this warning." );
232228 serverClientId = params .getClientId ();
233229 }
234230
235- if (Strings . isNullOrEmpty (serverClientId )) {
231+ if (isNullOrEmpty (serverClientId )) {
236232 // Only requests a clientId if google-services.json was present and parsed
237233 // by the google-services Gradle script.
238234 // TODO(jackson): Perhaps we should provide a mechanism to override this
@@ -246,7 +242,7 @@ public void init(@NonNull Messages.InitParams params) {
246242 serverClientId = context .getString (webClientIdIdentifier );
247243 }
248244 }
249- if (!Strings . isNullOrEmpty (serverClientId )) {
245+ if (!isNullOrEmpty (serverClientId )) {
250246 optionsBuilder .requestIdToken (serverClientId );
251247 optionsBuilder .requestServerAuthCode (
252248 serverClientId , params .getForceCodeForRefreshToken ());
@@ -255,7 +251,7 @@ public void init(@NonNull Messages.InitParams params) {
255251 for (String scope : requestedScopes ) {
256252 optionsBuilder .requestScopes (new Scope (scope ));
257253 }
258- if (!Strings . isNullOrEmpty (params .getHostedDomain ())) {
254+ if (!isNullOrEmpty (params .getHostedDomain ())) {
259255 optionsBuilder .setHostedDomain (params .getHostedDomain ());
260256 }
261257
@@ -450,6 +446,10 @@ private void finishWithError(String errorCode, String errorMessage) {
450446 pendingOperation = null ;
451447 }
452448
449+ private static boolean isNullOrEmpty (@ Nullable String s ) {
450+ return s == null || s .isEmpty ();
451+ }
452+
453453 private static class PendingOperation {
454454 final @ NonNull String method ;
455455 final @ Nullable Messages .Result <Messages .UserData > userDataResult ;
@@ -478,37 +478,26 @@ private static class PendingOperation {
478478 }
479479 }
480480
481- /** Clears the token kept in the client side cache. */
481+ /**
482+ * Clears the token kept in the client side cache.
483+ *
484+ * <p>Runs on a background task queue.
485+ */
482486 @ Override
483- public void clearAuthCache (@ NonNull String token , @ NonNull Messages .VoidResult result ) {
484- Callable <Void > clearTokenTask =
485- () -> {
486- GoogleAuthUtil .clearToken (context , token );
487- return null ;
488- };
489-
490- backgroundTaskRunner .runInBackground (
491- clearTokenTask ,
492- clearTokenFuture -> {
493- try {
494- clearTokenFuture .get ();
495- result .success ();
496- } catch (ExecutionException e ) {
497- @ Nullable Throwable cause = e .getCause ();
498- result .error (
499- new FlutterError (
500- ERROR_REASON_EXCEPTION , cause == null ? null : cause .getMessage (), null ));
501- } catch (InterruptedException e ) {
502- result .error (new FlutterError (ERROR_REASON_EXCEPTION , e .getMessage (), null ));
503- Thread .currentThread ().interrupt ();
504- }
505- });
487+ public void clearAuthCache (@ NonNull String token ) {
488+ try {
489+ GoogleAuthUtil .clearToken (context , token );
490+ } catch (Exception e ) {
491+ throw new FlutterError (ERROR_REASON_EXCEPTION , e .getMessage (), null );
492+ }
506493 }
507494
508495 /**
509496 * Gets an OAuth access token with the scopes that were specified during initialization for the
510497 * user with the specified email address.
511498 *
499+ * <p>Runs on a background task queue.
500+ *
512501 * <p>If shouldRecoverAuth is set to true and user needs to recover authentication for method to
513502 * complete, the method will attempt to recover authentication and rerun method.
514503 */
@@ -517,53 +506,39 @@ public void getAccessToken(
517506 @ NonNull String email ,
518507 @ NonNull Boolean shouldRecoverAuth ,
519508 @ NonNull Messages .Result <String > result ) {
520- Callable <String > getTokenTask =
521- () -> {
522- Account account = new Account (email , "com.google" );
523- String scopesStr = "oauth2:" + Joiner .on (' ' ).join (requestedScopes );
524- return GoogleAuthUtil .getToken (context , account , scopesStr );
525- };
526-
527- // Background task runner has a single thread effectively serializing
528- // the getToken calls. 1p apps can then enjoy the token cache if multiple
529- // getToken calls are coming in.
530- backgroundTaskRunner .runInBackground (
531- getTokenTask ,
532- tokenFuture -> {
533- try {
534- result .success (tokenFuture .get ());
535- } catch (ExecutionException e ) {
536- if (e .getCause () instanceof UserRecoverableAuthException ) {
537- if (shouldRecoverAuth && pendingOperation == null ) {
538- Activity activity = getActivity ();
539- if (activity == null ) {
540- result .error (
541- new FlutterError (
542- ERROR_USER_RECOVERABLE_AUTH ,
543- "Cannot recover auth because app is not in foreground. "
544- + e .getLocalizedMessage (),
545- null ));
546- } else {
547- checkAndSetPendingAccessTokenOperation ("getTokens" , result , email );
548- Intent recoveryIntent =
549- ((UserRecoverableAuthException ) e .getCause ()).getIntent ();
550- activity .startActivityForResult (recoveryIntent , REQUEST_CODE_RECOVER_AUTH );
551- }
552- } else {
509+ try {
510+ Account account = new Account (email , "com.google" );
511+ String scopesStr = "oauth2:" + String .join (" " , requestedScopes );
512+ String token = GoogleAuthUtil .getToken (context , account , scopesStr );
513+ result .success (token );
514+ } catch (UserRecoverableAuthException e ) {
515+ // This method runs in a background task queue; hop to the main thread for interactions with
516+ // plugin state and activities.
517+ final Handler handler = new Handler (Looper .getMainLooper ());
518+ handler .post (
519+ () -> {
520+ if (shouldRecoverAuth && pendingOperation == null ) {
521+ Activity activity = getActivity ();
522+ if (activity == null ) {
553523 result .error (
554- new FlutterError (ERROR_USER_RECOVERABLE_AUTH , e .getLocalizedMessage (), null ));
524+ new FlutterError (
525+ ERROR_USER_RECOVERABLE_AUTH ,
526+ "Cannot recover auth because app is not in foreground. "
527+ + e .getLocalizedMessage (),
528+ null ));
529+ } else {
530+ checkAndSetPendingAccessTokenOperation ("getTokens" , result , email );
531+ Intent recoveryIntent = e .getIntent ();
532+ activity .startActivityForResult (recoveryIntent , REQUEST_CODE_RECOVER_AUTH );
555533 }
556534 } else {
557- @ Nullable Throwable cause = e .getCause ();
558535 result .error (
559- new FlutterError (
560- ERROR_REASON_EXCEPTION , cause == null ? null : cause .getMessage (), null ));
536+ new FlutterError (ERROR_USER_RECOVERABLE_AUTH , e .getLocalizedMessage (), null ));
561537 }
562- } catch (InterruptedException e ) {
563- result .error (new FlutterError (ERROR_REASON_EXCEPTION , e .getMessage (), null ));
564- Thread .currentThread ().interrupt ();
565- }
566- });
538+ });
539+ } catch (Exception e ) {
540+ result .error (new FlutterError (ERROR_REASON_EXCEPTION , e .getMessage (), null ));
541+ }
567542 }
568543
569544 @ Override
0 commit comments