[google_sign_in] Migrate to Android v2 embedder. (#2624)

diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md
index a17b384..1a3011f 100644
--- a/packages/google_sign_in/google_sign_in/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 4.4.0
+
+* Migrate to Android v2 embedder.
+
 ## 4.3.0
 
 * Add support for method introduced in `google_sign_in_platform_interface` 1.1.0.
diff --git a/packages/google_sign_in/google_sign_in/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java b/packages/google_sign_in/google_sign_in/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
index ebebfa0..d3ea915 100755
--- a/packages/google_sign_in/google_sign_in/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
+++ b/packages/google_sign_in/google_sign_in/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
@@ -8,6 +8,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import com.google.android.gms.auth.GoogleAuthUtil;
 import com.google.android.gms.auth.UserRecoverableAuthException;
@@ -24,6 +25,10 @@
 import com.google.android.gms.tasks.Task;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.embedding.engine.plugins.activity.ActivityAware;
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
+import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -38,7 +43,7 @@
 import java.util.concurrent.Future;
 
 /** Google sign-in plugin for Flutter. */
-public class GoogleSignInPlugin implements MethodCallHandler {
+public class GoogleSignInPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
   private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in";
 
   private static final String METHOD_INIT = "init";
@@ -51,17 +56,76 @@
   private static final String METHOD_CLEAR_AUTH_CACHE = "clearAuthCache";
   private static final String METHOD_REQUEST_SCOPES = "requestScopes";
 
-  private final IDelegate delegate;
+  private Delegate delegate;
+  private MethodChannel channel;
+  private ActivityPluginBinding activityPluginBinding;
 
   public static void registerWith(PluginRegistry.Registrar registrar) {
-    final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
-    final GoogleSignInPlugin instance =
-        new GoogleSignInPlugin(registrar, new GoogleSignInWrapper());
-    channel.setMethodCallHandler(instance);
+    GoogleSignInPlugin instance = new GoogleSignInPlugin();
+    instance.initInstance(registrar.messenger(), registrar.context(), new GoogleSignInWrapper());
+    instance.setUpRegistrar(registrar);
   }
 
-  GoogleSignInPlugin(PluginRegistry.Registrar registrar, GoogleSignInWrapper googleSignInWrapper) {
-    delegate = new Delegate(registrar, googleSignInWrapper);
+  @VisibleForTesting
+  public void initInstance(
+      BinaryMessenger messenger, Context context, GoogleSignInWrapper googleSignInWrapper) {
+    channel = new MethodChannel(messenger, CHANNEL_NAME);
+    delegate = new Delegate(context, googleSignInWrapper);
+    channel.setMethodCallHandler(this);
+  }
+
+  @VisibleForTesting
+  public void setUpRegistrar(PluginRegistry.Registrar registrar) {
+    delegate.setUpRegistrar(registrar);
+  }
+
+  private void dispose() {
+    delegate = null;
+    channel.setMethodCallHandler(null);
+    channel = null;
+  }
+
+  private void attachToActivity(ActivityPluginBinding activityPluginBinding) {
+    this.activityPluginBinding = activityPluginBinding;
+    activityPluginBinding.addActivityResultListener(delegate);
+    delegate.setActivity(activityPluginBinding.getActivity());
+  }
+
+  private void disposeActivity() {
+    activityPluginBinding.removeActivityResultListener(delegate);
+    delegate.setActivity(null);
+    activityPluginBinding = null;
+  }
+
+  @Override
+  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
+    initInstance(
+        binding.getBinaryMessenger(), binding.getApplicationContext(), new GoogleSignInWrapper());
+  }
+
+  @Override
+  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
+    dispose();
+  }
+
+  @Override
+  public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
+    attachToActivity(activityPluginBinding);
+  }
+
+  @Override
+  public void onDetachedFromActivityForConfigChanges() {
+    disposeActivity();
+  }
+
+  @Override
+  public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
+    attachToActivity(activityPluginBinding);
+  }
+
+  @Override
+  public void onDetachedFromActivity() {
+    disposeActivity();
   }
 
   @Override
@@ -195,7 +259,11 @@
     private static final String DEFAULT_SIGN_IN = "SignInOption.standard";
     private static final String DEFAULT_GAMES_SIGN_IN = "SignInOption.games";
 
-    private final PluginRegistry.Registrar registrar;
+    private final Context context;
+    // Only set registrar for v1 embedder.
+    private PluginRegistry.Registrar registrar;
+    // Only set activity for v2 embedder. Always access activity from getActivity() method.
+    private Activity activity;
     private final BackgroundTaskRunner backgroundTaskRunner = new BackgroundTaskRunner(1);
     private final GoogleSignInWrapper googleSignInWrapper;
 
@@ -203,12 +271,25 @@
     private List<String> requestedScopes;
     private PendingOperation pendingOperation;
 
-    public Delegate(PluginRegistry.Registrar registrar, GoogleSignInWrapper googleSignInWrapper) {
-      this.registrar = registrar;
+    public Delegate(Context context, GoogleSignInWrapper googleSignInWrapper) {
+      this.context = context;
       this.googleSignInWrapper = googleSignInWrapper;
+    }
+
+    public void setUpRegistrar(PluginRegistry.Registrar registrar) {
+      this.registrar = registrar;
       registrar.addActivityResultListener(this);
     }
 
+    public void setActivity(Activity activity) {
+      this.activity = activity;
+    }
+
+    // Only access activity with this method.
+    public Activity getActivity() {
+      return registrar != null ? registrar.activity() : activity;
+    }
+
     private void checkAndSetPendingOperation(String method, Result result) {
       checkAndSetPendingOperation(method, result, null);
     }
@@ -249,13 +330,11 @@
         // TODO(jackson): Perhaps we should provide a mechanism to override this
         // behavior.
         int clientIdIdentifier =
-            registrar
-                .context()
+            context
                 .getResources()
-                .getIdentifier(
-                    "default_web_client_id", "string", registrar.context().getPackageName());
+                .getIdentifier("default_web_client_id", "string", context.getPackageName());
         if (clientIdIdentifier != 0) {
-          optionsBuilder.requestIdToken(registrar.context().getString(clientIdIdentifier));
+          optionsBuilder.requestIdToken(context.getString(clientIdIdentifier));
         }
         for (String scope : requestedScopes) {
           optionsBuilder.requestScopes(new Scope(scope));
@@ -265,7 +344,7 @@
         }
 
         this.requestedScopes = requestedScopes;
-        signInClient = GoogleSignIn.getClient(registrar.context(), optionsBuilder.build());
+        signInClient = GoogleSignIn.getClient(context, optionsBuilder.build());
         result.success(null);
       } catch (Exception e) {
         result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
@@ -300,13 +379,13 @@
      */
     @Override
     public void signIn(Result result) {
-      if (registrar.activity() == null) {
+      if (getActivity() == null) {
         throw new IllegalStateException("signIn needs a foreground activity");
       }
       checkAndSetPendingOperation(METHOD_SIGN_IN, result);
 
       Intent signInIntent = signInClient.getSignInIntent();
-      registrar.activity().startActivityForResult(signInIntent, REQUEST_CODE_SIGNIN);
+      getActivity().startActivityForResult(signInIntent, REQUEST_CODE_SIGNIN);
     }
 
     /**
@@ -355,7 +434,7 @@
     /** Checks if there is a signed in user. */
     @Override
     public void isSignedIn(final Result result) {
-      boolean value = GoogleSignIn.getLastSignedInAccount(registrar.context()) != null;
+      boolean value = GoogleSignIn.getLastSignedInAccount(context) != null;
       result.success(value);
     }
 
@@ -363,7 +442,7 @@
     public void requestScopes(Result result, List<String> scopes) {
       checkAndSetPendingOperation(METHOD_REQUEST_SCOPES, result);
 
-      GoogleSignInAccount account = googleSignInWrapper.getLastSignedInAccount(registrar.context());
+      GoogleSignInAccount account = googleSignInWrapper.getLastSignedInAccount(context);
       if (account == null) {
         result.error(ERROR_REASON_SIGN_IN_REQUIRED, "No account to grant scopes.", null);
         return;
@@ -384,10 +463,7 @@
       }
 
       googleSignInWrapper.requestPermissions(
-          registrar.activity(),
-          REQUEST_CODE_REQUEST_SCOPE,
-          account,
-          wrappedScopes.toArray(new Scope[0]));
+          getActivity(), REQUEST_CODE_REQUEST_SCOPE, account, wrappedScopes.toArray(new Scope[0]));
     }
 
     private void onSignInResult(Task<GoogleSignInAccount> completedTask) {
@@ -456,7 +532,7 @@
           new Callable<Void>() {
             @Override
             public Void call() throws Exception {
-              GoogleAuthUtil.clearToken(registrar.context(), token);
+              GoogleAuthUtil.clearToken(context, token);
               return null;
             }
           };
@@ -499,7 +575,7 @@
             public String call() throws Exception {
               Account account = new Account(email, "com.google");
               String scopesStr = "oauth2:" + Joiner.on(' ').join(requestedScopes);
-              return GoogleAuthUtil.getToken(registrar.context(), account, scopesStr);
+              return GoogleAuthUtil.getToken(context, account, scopesStr);
             }
           };
 
@@ -519,7 +595,7 @@
               } catch (ExecutionException e) {
                 if (e.getCause() instanceof UserRecoverableAuthException) {
                   if (shouldRecoverAuth && pendingOperation == null) {
-                    Activity activity = registrar.activity();
+                    Activity activity = getActivity();
                     if (activity == null) {
                       result.error(
                           ERROR_USER_RECOVERABLE_AUTH,
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/main/AndroidManifest.xml b/packages/google_sign_in/google_sign_in/example/android/app/src/main/AndroidManifest.xml
index 7e93af7..df80f82 100755
--- a/packages/google_sign_in/google_sign_in/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/google_sign_in/google_sign_in/example/android/app/src/main/AndroidManifest.xml
@@ -3,17 +3,23 @@
 
     <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application android:name="io.flutter.app.FlutterApplication" android:label="Google Sign-In Example" android:icon="@mipmap/ic_launcher">
-        <activity android:name=".MainActivity"
-                  android:launchMode="singleTop"
+    <application>
+        <activity android:name="io.flutter.embedding.android.FlutterActivity"
                   android:theme="@android:style/Theme.Black.NoTitleBar"
                   android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
                   android:hardwareAccelerated="true"
                   android:windowSoftInputMode="adjustResize">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".EmbeddingV1Activity"
+                  android:theme="@android:style/Theme.Black.NoTitleBar"
+                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+                  android:hardwareAccelerated="true"
+                  android:windowSoftInputMode="adjustResize">
+        </activity>
+        <meta-data android:name="flutterEmbedding" android:value="2"/>
     </application>
 </manifest>
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java
new file mode 100644
index 0000000..f7ea0c4
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlesigninexample;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.googlesignin.GoogleSignInPlugin;
+import io.flutter.view.FlutterMain;
+
+public class EmbeddingV1Activity extends FlutterActivity {
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    FlutterMain.startInitialization(this);
+    super.onCreate(savedInstanceState);
+    GoogleSignInPlugin.registerWith(registrarFor("io.flutter.plugins.googlesignin"));
+  }
+}
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java
new file mode 100644
index 0000000..8bddbff
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlesigninexample;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.e2e.FlutterRunner;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(FlutterRunner.class)
+public class EmbeddingV1ActivityTest {
+  @Rule
+  public ActivityTestRule<EmbeddingV1Activity> rule =
+      new ActivityTestRule<>(EmbeddingV1Activity.class);
+}
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java
new file mode 100644
index 0000000..77cdcee
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java
@@ -0,0 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlesigninexample;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.e2e.FlutterRunner;
+import io.flutter.embedding.android.FlutterActivity;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(FlutterRunner.class)
+public class FlutterActivityTest {
+  @Rule
+  public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
+}
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/MainActivity.java b/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/MainActivity.java
deleted file mode 100644
index 026cec2..0000000
--- a/packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/MainActivity.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.googlesigninexample;
-
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
-  @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    GeneratedPluginRegistrant.registerWith(this);
-  }
-}
diff --git a/packages/google_sign_in/google_sign_in/example/android/app/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInPluginTests.java b/packages/google_sign_in/google_sign_in/example/android/app/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInPluginTests.java
index bd8e37a..1413063 100644
--- a/packages/google_sign_in/google_sign_in/example/android/app/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInPluginTests.java
+++ b/packages/google_sign_in/google_sign_in/example/android/app/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInPluginTests.java
@@ -45,7 +45,9 @@
     when(mockRegistrar.messenger()).thenReturn(mockMessenger);
     when(mockRegistrar.context()).thenReturn(mockContext);
     when(mockRegistrar.activity()).thenReturn(mockActivity);
-    plugin = new GoogleSignInPlugin(mockRegistrar, mockGoogleSignIn);
+    plugin = new GoogleSignInPlugin();
+    plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn);
+    plugin.setUpRegistrar(mockRegistrar);
   }
 
   @Test
diff --git a/packages/google_sign_in/google_sign_in/example/pubspec.yaml b/packages/google_sign_in/google_sign_in/example/pubspec.yaml
index 1530af2..e3ab95e 100755
--- a/packages/google_sign_in/google_sign_in/example/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in/example/pubspec.yaml
@@ -10,6 +10,13 @@
 
 dev_dependencies:
   pedantic: ^1.8.0
+  e2e:  ^0.2.1
+  flutter_driver:
+    sdk: flutter
 
 flutter:
   uses-material-design: true
+
+environment:
+  sdk: ">=2.0.0-dev.28.0 <3.0.0"
+  flutter: ">=1.12.13+hotfix.4 <2.0.0"
diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml
index 860369d..055b7c5 100644
--- a/packages/google_sign_in/google_sign_in/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for Google Sign-In, a secure authentication system
   for signing in with a Google account on Android and iOS.
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in
-version: 4.3.0
+version: 4.4.0
 
 flutter:
   plugin:
@@ -29,9 +29,12 @@
 
 dev_dependencies:
   http: ^0.12.0
+  flutter_driver:
+    sdk: flutter
   flutter_test:
     sdk: flutter
   pedantic: ^1.8.0
+  e2e: ^0.2.1
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_e2e.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_e2e.dart
new file mode 100644
index 0000000..0c6431f
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_e2e.dart
@@ -0,0 +1,12 @@
+import 'package:e2e/e2e.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+
+void main() {
+  E2EWidgetsFlutterBinding.ensureInitialized();
+
+  testWidgets('Can initialize the plugin', (WidgetTester tester) async {
+    GoogleSignIn signIn = GoogleSignIn();
+    expect(signIn, isNotNull);
+  });
+}
diff --git a/packages/google_sign_in/google_sign_in/test_driver/google_sign_in_e2e_test.dart b/packages/google_sign_in/google_sign_in/test_driver/google_sign_in_e2e_test.dart
new file mode 100644
index 0000000..9f1704f
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in/test_driver/google_sign_in_e2e_test.dart
@@ -0,0 +1,15 @@
+// Copyright 2020, the Chromium project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+import 'package:flutter_driver/flutter_driver.dart';
+
+Future<void> main() async {
+  final FlutterDriver driver = await FlutterDriver.connect();
+  final String result =
+      await driver.requestData(null, timeout: const Duration(minutes: 1));
+  await driver.close();
+  exit(result == 'pass' ? 0 : 1);
+}