[flutter_webview] Migrate to the new embedding (#2169)

This depends on a new bugfix on the engine for text input to work with
the new embedding (flutter/engine#13015).
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 9822ad5..7710f74 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.3.15
+
+* Add support for the v2 Android embedding. This shouldn't affect existing
+  functionality. Plugin authors who use the V2 embedding can now register the
+  plugin and expect that it correctly responds to app lifecycle changes.
+
 ## 0.3.14+2
 
 * Define clang module for iOS.
@@ -13,7 +19,7 @@
 ## 0.3.13
 
 * Add an optional `userAgent` property to set a custom User Agent.
-  
+
 ## 0.3.12+1
 
 * Temporarily revert getTitle (doing this as a patch bump shortly after publishing).
diff --git a/packages/webview_flutter/android/build.gradle b/packages/webview_flutter/android/build.gradle
index 4fe7629..0104ede 100644
--- a/packages/webview_flutter/android/build.gradle
+++ b/packages/webview_flutter/android/build.gradle
@@ -50,3 +50,28 @@
         implementation 'androidx.webkit:webkit:1.0.0'
     }
 }
+
+// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
+afterEvaluate {
+    def containsEmbeddingDependencies = false
+    for (def configuration : configurations.all) {
+        for (def dependency : configuration.dependencies) {
+            if (dependency.group == 'io.flutter' &&
+                dependency.name.startsWith('flutter_embedding') &&
+                dependency.isTransitive())
+            {
+                containsEmbeddingDependencies = true
+                break
+            }
+        }
+    }
+    if (!containsEmbeddingDependencies) {
+        android {
+            dependencies {
+                def lifecycle_version = "2.1.0"
+                api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+                api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/webview_flutter/android/gradle.properties b/packages/webview_flutter/android/gradle.properties
index 8bd86f6..08f2b5f 100644
--- a/packages/webview_flutter/android/gradle.properties
+++ b/packages/webview_flutter/android/gradle.properties
@@ -1 +1,3 @@
 org.gradle.jvmargs=-Xmx1536M
+android.enableJetifier=true
+android.useAndroidX=true
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java
index 908f877..86b4fd4 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java
@@ -15,17 +15,11 @@
 import io.flutter.plugin.common.MethodChannel.Result;
 
 class FlutterCookieManager implements MethodCallHandler {
+  private final MethodChannel methodChannel;
 
-  private FlutterCookieManager() {
-    // Do not instantiate.
-    // This class should only be used in context of a BinaryMessenger.
-    // Use FlutterCookieManager#registerWith instead.
-  }
-
-  static void registerWith(BinaryMessenger messenger) {
-    MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
-    FlutterCookieManager cookieManager = new FlutterCookieManager();
-    methodChannel.setMethodCallHandler(cookieManager);
+  FlutterCookieManager(BinaryMessenger messenger) {
+    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
+    methodChannel.setMethodCallHandler(this);
   }
 
   @Override
@@ -39,6 +33,10 @@
     }
   }
 
+  void dispose() {
+    methodChannel.setMethodCallHandler(null);
+  }
+
   private static void clearCookies(final Result result) {
     CookieManager cookieManager = CookieManager.getInstance();
     final boolean hasCookies = cookieManager.hasCookies();
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index a7f2db3..ac326ed 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -12,6 +12,8 @@
 import android.view.View;
 import android.webkit.WebStorage;
 import android.webkit.WebViewClient;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
@@ -36,7 +38,7 @@
       BinaryMessenger messenger,
       int id,
       Map<String, Object> params,
-      final View containerView) {
+      @Nullable View containerView) {
 
     DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
     DisplayManager displayManager =
@@ -96,6 +98,16 @@
   }
 
   @Override
+  public void onFlutterViewAttached(@NonNull View flutterView) {
+    webView.setContainerView(flutterView);
+  }
+
+  @Override
+  public void onFlutterViewDetached() {
+    webView.setContainerView(null);
+  }
+
+  @Override
   public void onMethodCall(MethodCall methodCall, Result result) {
     switch (methodCall.method) {
       case "loadUrl":
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java
index 9275c38..e04d566 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java
@@ -7,9 +7,11 @@
 import static android.content.Context.INPUT_METHOD_SERVICE;
 
 import android.content.Context;
+import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.webkit.WebView;
+import androidx.annotation.Nullable;
 
 /**
  * A WebView subclass that mirrors the same implementation hacks that the system WebView does in
@@ -22,16 +24,29 @@
  * <p>See also {@link ThreadedInputConnectionProxyAdapterView}.
  */
 final class InputAwareWebView extends WebView {
-  private final View containerView;
-
+  private static final String TAG = "InputAwareWebView";
   private View threadedInputConnectionProxyView;
   private ThreadedInputConnectionProxyAdapterView proxyAdapterView;
+  private @Nullable View containerView;
 
-  InputAwareWebView(Context context, View containerView) {
+  InputAwareWebView(Context context, @Nullable View containerView) {
     super(context);
     this.containerView = containerView;
   }
 
+  void setContainerView(@Nullable View containerView) {
+    this.containerView = containerView;
+
+    if (proxyAdapterView == null) {
+      return;
+    }
+
+    Log.w(TAG, "The containerView has changed while the proxyAdapterView exists.");
+    if (containerView != null) {
+      setInputConnectionTarget(proxyAdapterView);
+    }
+  }
+
   /**
    * Set our proxy adapter view to use its cached input connection instead of creating new ones.
    *
@@ -81,6 +96,12 @@
       // This isn't a new ThreadedInputConnectionProxyView. Ignore it.
       return super.checkInputConnectionProxy(view);
     }
+    if (containerView == null) {
+      Log.e(
+          TAG,
+          "Can't create a proxy view because there's no container view. Text input may not work.");
+      return super.checkInputConnectionProxy(view);
+    }
 
     // We've never seen this before, so we make the assumption that this is WebView's
     // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could
@@ -120,6 +141,10 @@
       // No need to reset the InputConnection to the default thread if we've never changed it.
       return;
     }
+    if (containerView == null) {
+      Log.e(TAG, "Can't reset the input connection to the container view because there is none.");
+      return;
+    }
     setInputConnectionTarget(/*targetView=*/ containerView);
   }
 
@@ -132,6 +157,13 @@
    * InputConnections should be created on.
    */
   private void setInputConnectionTarget(final View targetView) {
+    if (containerView == null) {
+      Log.e(
+          TAG,
+          "Can't set the input connection target because there is no containerView to use as a handler.");
+      return;
+    }
+
     targetView.requestFocus();
     containerView.post(
         new Runnable() {
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java
index 6fdc36f..fe62e3a 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFactory.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.view.View;
+import androidx.annotation.Nullable;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.StandardMessageCodec;
 import io.flutter.plugin.platform.PlatformView;
@@ -14,9 +15,9 @@
 
 public final class WebViewFactory extends PlatformViewFactory {
   private final BinaryMessenger messenger;
-  private final View containerView;
+  private @Nullable final View containerView;
 
-  WebViewFactory(BinaryMessenger messenger, View containerView) {
+  WebViewFactory(BinaryMessenger messenger, @Nullable View containerView) {
     super(StandardMessageCodec.INSTANCE);
     this.messenger = messenger;
     this.containerView = containerView;
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
index 1717754..dcce796 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -4,17 +4,68 @@
 
 package io.flutter.plugins.webviewflutter;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.PluginRegistry.Registrar;
 
-/** WebViewFlutterPlugin */
-public class WebViewFlutterPlugin {
-  /** Plugin registration. */
+/**
+ * Java platform implementation of the webview_flutter plugin.
+ *
+ * <p>Register this in an add to app scenario to gracefully handle activity and context changes.
+ *
+ * <p>Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common}
+ * package instead.
+ */
+public class WebViewFlutterPlugin implements FlutterPlugin {
+
+  private @Nullable FlutterCookieManager flutterCookieManager;
+
+  /**
+   * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to
+   * register it.
+   *
+   * <p>Registration should eventually be handled automatically by v2 of the
+   * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694
+   */
+  public WebViewFlutterPlugin() {}
+
+  /**
+   * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
+   * package.
+   *
+   * <p>Calling this automatically initializes the plugin. However plugins initialized this way
+   * won't react to changes in activity or context, unlike {@link CameraPlugin}.
+   */
   public static void registerWith(Registrar registrar) {
     registrar
         .platformViewRegistry()
         .registerViewFactory(
             "plugins.flutter.io/webview",
             new WebViewFactory(registrar.messenger(), registrar.view()));
-    FlutterCookieManager.registerWith(registrar.messenger());
+    new FlutterCookieManager(registrar.messenger());
+  }
+
+  @Override
+  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
+    BinaryMessenger messenger = binding.getFlutterEngine().getDartExecutor();
+    binding
+        .getFlutterEngine()
+        .getPlatformViewsController()
+        .getRegistry()
+        .registerViewFactory(
+            "plugins.flutter.io/webview", new WebViewFactory(messenger, /*containerView=*/ null));
+    flutterCookieManager = new FlutterCookieManager(messenger);
+  }
+
+  @Override
+  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
+    if (flutterCookieManager == null) {
+      return;
+    }
+
+    flutterCookieManager.dispose();
+    flutterCookieManager = null;
   }
 }
diff --git a/packages/webview_flutter/example/android/app/build.gradle b/packages/webview_flutter/example/android/app/build.gradle
index 79a69ac..706d501 100644
--- a/packages/webview_flutter/example/android/app/build.gradle
+++ b/packages/webview_flutter/example/android/app/build.gradle
@@ -56,6 +56,7 @@
 
 dependencies {
     testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'androidx.test:runner:1.1.1'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test:rules:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 }
diff --git a/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java
new file mode 100644
index 0000000..fe10c61
--- /dev/null
+++ b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java
@@ -0,0 +1,13 @@
+package io.flutter.plugins.webviewflutterexample;
+
+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/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java
new file mode 100644
index 0000000..a0bd4fe
--- /dev/null
+++ b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java
@@ -0,0 +1,11 @@
+package io.flutter.plugins.webviewflutterexample;
+
+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 MainActivityTest {
+  @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
+}
diff --git a/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml
index 8fcbcd3..fd570ac 100644
--- a/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml
@@ -1,39 +1,48 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.flutter.plugins.webviewflutterexample">
+  package="io.flutter.plugins.webviewflutterexample">
 
-    <!-- The INTERNET permission is required for development. Specifically,
-         flutter needs it to communicate with the running application
-         to allow setting breakpoints, to provide hot reload, etc.
-    -->
-    <uses-permission android:name="android.permission.INTERNET"/>
+  <!-- io.flutter.app.FlutterApplication is an android.app.Application that
+       calls FlutterMain.startInitialization(this); in its onCreate method.
+       In most cases you can leave this as-is, but you if you want to provide
+       additional functionality it is fine to subclass or reimplement
+       FlutterApplication and put your custom class here. -->
+  <application
+    android:icon="@mipmap/ic_launcher"
+    android:label="webview_flutter_example"
+    android:name="io.flutter.app.FlutterApplication">
+    <activity
+      android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+      android:exported="true"
+      android:hardwareAccelerated="true"
+      android:launchMode="singleTop"
+      android:name=".EmbeddingV1Activity"
+      android:theme="@style/LaunchTheme"
+      android:windowSoftInputMode="adjustResize">
+      <!-- This keeps the window background of the activity showing
+           until Flutter renders its first frame. It can be removed if
+           there is no splash screen (such as the default splash screen
+           defined in @style/LaunchTheme). -->
+      <meta-data
+        android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
+        android:value="true"/>
+    </activity>
+    <activity
+      android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+      android:hardwareAccelerated="true"
+      android:launchMode="singleTop"
+      android:name=".MainActivity"
+      android:theme="@style/LaunchTheme"
+      android:windowSoftInputMode="adjustResize">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+    </activity>
+  </application>
 
-    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
-         calls FlutterMain.startInitialization(this); in its onCreate method.
-         In most cases you can leave this as-is, but you if you want to provide
-         additional functionality it is fine to subclass or reimplement
-         FlutterApplication and put your custom class here. -->
-    <application
-        android:name="io.flutter.app.FlutterApplication"
-        android:label="webview_flutter_example"
-        android:icon="@mipmap/ic_launcher">
-        <activity
-            android:name=".MainActivity"
-            android:launchMode="singleTop"
-            android:theme="@style/LaunchTheme"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
-            android:hardwareAccelerated="true"
-            android:windowSoftInputMode="adjustResize">
-            <!-- This keeps the window background of the activity showing
-                 until Flutter renders its first frame. It can be removed if
-                 there is no splash screen (such as the default splash screen
-                 defined in @style/LaunchTheme). -->
-            <meta-data
-                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
-                android:value="true" />
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
+  <!-- The INTERNET permission is required for development. Specifically,
+       flutter needs it to communicate with the running application
+       to allow setting breakpoints, to provide hot reload, etc.
+  -->
+  <uses-permission android:name="android.permission.INTERNET"/>
 </manifest>
diff --git a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java
new file mode 100644
index 0000000..9b86893
--- /dev/null
+++ b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java
@@ -0,0 +1,17 @@
+// Copyright 2019 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.webviewflutterexample;
+
+import android.os.Bundle;
+import io.flutter.app.FlutterActivity;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+
+public class EmbeddingV1Activity extends FlutterActivity {
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    GeneratedPluginRegistrant.registerWith(this);
+  }
+}
diff --git a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java
index f935d00..1596844 100644
--- a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java
+++ b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java
@@ -4,14 +4,14 @@
 
 package io.flutter.plugins.webviewflutterexample;
 
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.plugins.webviewflutter.WebViewFlutterPlugin;
 
 public class MainActivity extends FlutterActivity {
+  // TODO(mklim): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694
   @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    GeneratedPluginRegistrant.registerWith(this);
+  public void configureFlutterEngine(FlutterEngine flutterEngine) {
+    flutterEngine.getPlugins().add(new WebViewFlutterPlugin());
   }
 }
diff --git a/packages/webview_flutter/example/android/gradle.properties b/packages/webview_flutter/example/android/gradle.properties
index ad8917e..a673820 100644
--- a/packages/webview_flutter/example/android/gradle.properties
+++ b/packages/webview_flutter/example/android/gradle.properties
@@ -1,2 +1,4 @@
 org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
\ No newline at end of file
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
diff --git a/packages/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/example/pubspec.yaml
index 8657cfd..0e24333 100644
--- a/packages/webview_flutter/example/pubspec.yaml
+++ b/packages/webview_flutter/example/pubspec.yaml
@@ -1,10 +1,11 @@
 name: webview_flutter_example
 description: Demonstrates how to use the webview_flutter plugin.
 
-version: 1.0.1
+version: 1.0.2
 
 environment:
   sdk: ">=2.0.0-dev.68.0 <3.0.0"
+  flutter: ">=1.9.1+hotfix.4 <2.0.0"
 
 dependencies:
   flutter:
@@ -17,6 +18,7 @@
     sdk: flutter
   flutter_driver:
     sdk: flutter
+  e2e: "^0.2.0"
 
 flutter:
   uses-material-design: true
diff --git a/packages/webview_flutter/example/test_driver/webview.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
similarity index 92%
rename from packages/webview_flutter/example/test_driver/webview.dart
rename to packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
index e24afd7..a5d4d7d 100644
--- a/packages/webview_flutter/example/test_driver/webview.dart
+++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
@@ -9,19 +9,17 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
-import 'package:flutter_driver/driver_extension.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:webview_flutter/webview_flutter.dart';
+import 'package:e2e/e2e.dart';
 
 void main() {
-  final Completer<String> allTestsCompleter = Completer<String>();
-  enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future);
-  tearDownAll(() => allTestsCompleter.complete(null));
+  E2EWidgetsFlutterBinding.ensureInitialized();
 
-  test('initalUrl', () async {
+  testWidgets('initalUrl', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -38,10 +36,10 @@
     expect(currentUrl, 'https://flutter.dev/');
   });
 
-  test('loadUrl', () async {
+  testWidgets('loadUrl', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -61,11 +59,11 @@
 
   // enable this once https://github.com/flutter/flutter/issues/31510
   // is resolved.
-  test('loadUrl with headers', () async {
+  testWidgets('loadUrl with headers', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
     final StreamController<String> pageLoads = StreamController<String>();
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -96,12 +94,12 @@
     expect(content.contains('flutter_test_header'), isTrue);
   });
 
-  test('JavaScriptChannel', () async {
+  testWidgets('JavaScriptChannel', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
     final Completer<void> pageLoaded = Completer<void>();
     final List<String> messagesReceived = <String>[];
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -137,7 +135,7 @@
     expect(messagesReceived, equals(<String>['hello']));
   });
 
-  test('resize webview', () async {
+  testWidgets('resize webview', (WidgetTester tester) async {
     final String resizeTest = '''
         <!DOCTYPE html><html>
         <head><title>Resize test</title>
@@ -184,7 +182,7 @@
       javascriptMode: JavascriptMode.unrestricted,
     );
 
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: Column(
@@ -204,7 +202,7 @@
 
     expect(resizeCompleter.isCompleted, false);
 
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: Column(
@@ -222,11 +220,11 @@
     await resizeCompleter.future;
   });
 
-  test('set custom userAgent', () async {
+  testWidgets('set custom userAgent', (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter1 =
         Completer<WebViewController>();
     final GlobalKey _globalKey = GlobalKey();
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -244,7 +242,7 @@
     final String customUserAgent1 = await _getUserAgent(controller1);
     expect(customUserAgent1, 'Custom_User_Agent1');
     // rebuild the WebView with a different user agent.
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -260,12 +258,13 @@
     expect(customUserAgent2, 'Custom_User_Agent2');
   });
 
-  test('use default platform userAgent after webView is rebuilt', () async {
+  testWidgets('use default platform userAgent after webView is rebuilt',
+      (WidgetTester tester) async {
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
     final GlobalKey _globalKey = GlobalKey();
     // Build the webView with no user agent to get the default platform user agent.
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -281,7 +280,7 @@
     final WebViewController controller = await controllerCompleter.future;
     final String defaultPlatformUserAgent = await _getUserAgent(controller);
     // rebuild the WebView with a custom user agent.
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -295,7 +294,7 @@
     final String customUserAgent = await _getUserAgent(controller);
     expect(customUserAgent, 'Custom_User_Agent');
     // rebuilds the WebView with no user agent.
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -341,12 +340,12 @@
       audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest));
     });
 
-    test('Auto media playback', () async {
+    testWidgets('Auto media playback', (WidgetTester tester) async {
       Completer<WebViewController> controllerCompleter =
           Completer<WebViewController>();
       Completer<void> pageLoaded = Completer<void>();
 
-      await pumpWidget(
+      await tester.pumpWidget(
         Directionality(
           textDirection: TextDirection.ltr,
           child: WebView(
@@ -373,7 +372,7 @@
       pageLoaded = Completer<void>();
 
       // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy
-      await pumpWidget(
+      await tester.pumpWidget(
         Directionality(
           textDirection: TextDirection.ltr,
           child: WebView(
@@ -399,13 +398,14 @@
       expect(isPaused, _webviewBool(true));
     });
 
-    test('Changes to initialMediaPlaybackPolocy are ignored', () async {
+    testWidgets('Changes to initialMediaPlaybackPolocy are ignored',
+        (WidgetTester tester) async {
       final Completer<WebViewController> controllerCompleter =
           Completer<WebViewController>();
       Completer<void> pageLoaded = Completer<void>();
 
       final GlobalKey key = GlobalKey();
-      await pumpWidget(
+      await tester.pumpWidget(
         Directionality(
           textDirection: TextDirection.ltr,
           child: WebView(
@@ -430,7 +430,7 @@
 
       pageLoaded = Completer<void>();
 
-      await pumpWidget(
+      await tester.pumpWidget(
         Directionality(
           textDirection: TextDirection.ltr,
           child: WebView(
@@ -458,7 +458,7 @@
     });
   });
 
-  test('getTitle', () async {
+  testWidgets('getTitle', (WidgetTester tester) async {
     final String getTitleTest = '''
         <!DOCTYPE html><html>
         <head><title>Some title</title>
@@ -473,7 +473,7 @@
     final Completer<WebViewController> controllerCompleter =
         Completer<WebViewController>();
 
-    await pumpWidget(
+    await tester.pumpWidget(
       Directionality(
         textDirection: TextDirection.ltr,
         child: WebView(
@@ -496,11 +496,6 @@
   });
 }
 
-Future<void> pumpWidget(Widget widget) {
-  runApp(widget);
-  return WidgetsBinding.instance.endOfFrame;
-}
-
 // JavaScript booleans evaluate to different string values on Android and iOS.
 // This utility method returns the string boolean value of the current platform.
 String _webviewBool(bool value) {
diff --git a/packages/webview_flutter/example/test_driver/webview_test.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart
similarity index 72%
rename from packages/webview_flutter/example/test_driver/webview_test.dart
rename to packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart
index b0d3305..2e5c27f 100644
--- a/packages/webview_flutter/example/test_driver/webview_test.dart
+++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart
@@ -3,11 +3,14 @@
 // 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();
-  await driver.requestData(null, timeout: const Duration(minutes: 1));
+  final String result =
+      await driver.requestData(null, timeout: const Duration(minutes: 1));
   driver.close();
+  exit(result == 'pass' ? 0 : 1);
 }
diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml
index 13ccef7..efddfd3 100644
--- a/packages/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/pubspec.yaml
@@ -1,12 +1,12 @@
 name: webview_flutter
 description: A Flutter plugin that provides a WebView widget on Android and iOS.
-version: 0.3.14+2
+version: 0.3.15
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
 
 environment:
   sdk: ">=2.0.0-dev.68.0 <3.0.0"
-  flutter: ">=1.5.0 <2.0.0"
+  flutter: ">=1.6.7 <2.0.0"
 
 dependencies:
   flutter: