66
77import android .accounts .Account ;
88import android .app .Activity ;
9+ import android .content .Context ;
910import android .content .Intent ;
11+ import androidx .annotation .VisibleForTesting ;
1012import com .google .android .gms .auth .GoogleAuthUtil ;
1113import com .google .android .gms .auth .UserRecoverableAuthException ;
1214import com .google .android .gms .auth .api .signin .GoogleSignIn ;
2729import io .flutter .plugin .common .MethodChannel .MethodCallHandler ;
2830import io .flutter .plugin .common .MethodChannel .Result ;
2931import io .flutter .plugin .common .PluginRegistry ;
32+ import java .util .ArrayList ;
3033import java .util .HashMap ;
3134import java .util .List ;
3235import java .util .Map ;
@@ -46,17 +49,19 @@ public class GoogleSignInPlugin implements MethodCallHandler {
4649 private static final String METHOD_DISCONNECT = "disconnect" ;
4750 private static final String METHOD_IS_SIGNED_IN = "isSignedIn" ;
4851 private static final String METHOD_CLEAR_AUTH_CACHE = "clearAuthCache" ;
52+ private static final String METHOD_REQUEST_SCOPES = "requestScopes" ;
4953
5054 private final IDelegate delegate ;
5155
5256 public static void registerWith (PluginRegistry .Registrar registrar ) {
5357 final MethodChannel channel = new MethodChannel (registrar .messenger (), CHANNEL_NAME );
54- final GoogleSignInPlugin instance = new GoogleSignInPlugin (registrar );
58+ final GoogleSignInPlugin instance =
59+ new GoogleSignInPlugin (registrar , new GoogleSignInWrapper ());
5560 channel .setMethodCallHandler (instance );
5661 }
5762
58- private GoogleSignInPlugin (PluginRegistry .Registrar registrar ) {
59- delegate = new Delegate (registrar );
63+ GoogleSignInPlugin (PluginRegistry .Registrar registrar , GoogleSignInWrapper googleSignInWrapper ) {
64+ delegate = new Delegate (registrar , googleSignInWrapper );
6065 }
6166
6267 @ Override
@@ -100,6 +105,11 @@ public void onMethodCall(MethodCall call, Result result) {
100105 delegate .isSignedIn (result );
101106 break ;
102107
108+ case METHOD_REQUEST_SCOPES :
109+ List <String > scopes = call .argument ("scopes" );
110+ delegate .requestScopes (result , scopes );
111+ break ;
112+
103113 default :
104114 result .notImplemented ();
105115 }
@@ -153,6 +163,9 @@ public void init(
153163
154164 /** Checks if there is a signed in user. */
155165 public void isSignedIn (Result result );
166+
167+ /** Prompts the user to grant an additional Oauth scopes. */
168+ public void requestScopes (final Result result , final List <String > scopes );
156169 }
157170
158171 /**
@@ -167,6 +180,7 @@ public void init(
167180 public static final class Delegate implements IDelegate , PluginRegistry .ActivityResultListener {
168181 private static final int REQUEST_CODE_SIGNIN = 53293 ;
169182 private static final int REQUEST_CODE_RECOVER_AUTH = 53294 ;
183+ @ VisibleForTesting static final int REQUEST_CODE_REQUEST_SCOPE = 53295 ;
170184
171185 private static final String ERROR_REASON_EXCEPTION = "exception" ;
172186 private static final String ERROR_REASON_STATUS = "status" ;
@@ -183,13 +197,15 @@ public static final class Delegate implements IDelegate, PluginRegistry.Activity
183197
184198 private final PluginRegistry .Registrar registrar ;
185199 private final BackgroundTaskRunner backgroundTaskRunner = new BackgroundTaskRunner (1 );
200+ private final GoogleSignInWrapper googleSignInWrapper ;
186201
187202 private GoogleSignInClient signInClient ;
188203 private List <String > requestedScopes ;
189204 private PendingOperation pendingOperation ;
190205
191- public Delegate (PluginRegistry .Registrar registrar ) {
206+ public Delegate (PluginRegistry .Registrar registrar , GoogleSignInWrapper googleSignInWrapper ) {
192207 this .registrar = registrar ;
208+ this .googleSignInWrapper = googleSignInWrapper ;
193209 registrar .addActivityResultListener (this );
194210 }
195211
@@ -343,6 +359,37 @@ public void isSignedIn(final Result result) {
343359 result .success (value );
344360 }
345361
362+ @ Override
363+ public void requestScopes (Result result , List <String > scopes ) {
364+ checkAndSetPendingOperation (METHOD_REQUEST_SCOPES , result );
365+
366+ GoogleSignInAccount account = googleSignInWrapper .getLastSignedInAccount (registrar .context ());
367+ if (account == null ) {
368+ result .error (ERROR_REASON_SIGN_IN_REQUIRED , "No account to grant scopes." , null );
369+ return ;
370+ }
371+
372+ List <Scope > wrappedScopes = new ArrayList <>();
373+
374+ for (String scope : scopes ) {
375+ Scope wrappedScope = new Scope (scope );
376+ if (!googleSignInWrapper .hasPermissions (account , wrappedScope )) {
377+ wrappedScopes .add (wrappedScope );
378+ }
379+ }
380+
381+ if (wrappedScopes .isEmpty ()) {
382+ result .success (true );
383+ return ;
384+ }
385+
386+ googleSignInWrapper .requestPermissions (
387+ registrar .activity (),
388+ REQUEST_CODE_REQUEST_SCOPE ,
389+ account ,
390+ wrappedScopes .toArray (new Scope [0 ]));
391+ }
392+
346393 private void onSignInResult (Task <GoogleSignInAccount > completedTask ) {
347394 try {
348395 GoogleSignInAccount account = completedTask .getResult (ApiException .class );
@@ -527,9 +574,37 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
527574 finishWithError (ERROR_REASON_SIGN_IN_FAILED , "Signin failed" );
528575 }
529576 return true ;
577+ case REQUEST_CODE_REQUEST_SCOPE :
578+ finishWithSuccess (resultCode == Activity .RESULT_OK );
579+ return true ;
530580 default :
531581 return false ;
532582 }
533583 }
534584 }
535585}
586+
587+ /**
588+ * A wrapper object that calls static method in GoogleSignIn.
589+ *
590+ * <p>Because GoogleSignIn uses static method mostly, which is hard for unit testing. We use this
591+ * wrapper class to use instance method which calls the corresponding GoogleSignIn static methods.
592+ *
593+ * <p>Warning! This class should stay true that each method calls a GoogleSignIn static method with
594+ * the same name and same parameters.
595+ */
596+ class GoogleSignInWrapper {
597+
598+ GoogleSignInAccount getLastSignedInAccount (Context context ) {
599+ return GoogleSignIn .getLastSignedInAccount (context );
600+ }
601+
602+ boolean hasPermissions (GoogleSignInAccount account , Scope scope ) {
603+ return GoogleSignIn .hasPermissions (account , scope );
604+ }
605+
606+ void requestPermissions (
607+ Activity activity , int requestCode , GoogleSignInAccount account , Scope [] scopes ) {
608+ GoogleSignIn .requestPermissions (activity , requestCode , account , scopes );
609+ }
610+ }
0 commit comments