[webview_flutter] Add Android implementations for new cookie manager, to allow setting cookies directly and on webview creation.  (#4557)

diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index ce73ab2..7bd3387 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.8.0
+
+* Implements new cookie manager for setting cookies and providing initial cookies.
+
 ## 2.7.0
 
 * Adds support for the `loadRequest` method from the platform interface.
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java
new file mode 100644
index 0000000..3e38ce9
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java
@@ -0,0 +1,29 @@
+// Copyright 2013 The Flutter 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.webviewflutter;
+
+import android.os.Build;
+import android.webkit.CookieManager;
+
+class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerHostApi {
+  @Override
+  public void clearCookies(GeneratedAndroidWebView.Result<Boolean> result) {
+    CookieManager cookieManager = CookieManager.getInstance();
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      cookieManager.removeAllCookies(result::success);
+    } else {
+      final boolean hasCookies = cookieManager.hasCookies();
+      if (hasCookies) {
+        cookieManager.removeAllCookie();
+      }
+      result.success(hasCookies);
+    }
+  }
+
+  @Override
+  public void setCookie(String url, String value) {
+    CookieManager.getInstance().setCookie(url, value);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java
deleted file mode 100644
index df3f21d..0000000
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2013 The Flutter 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.webviewflutter;
-
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
-import android.webkit.CookieManager;
-import android.webkit.ValueCallback;
-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;
-import io.flutter.plugin.common.MethodChannel.Result;
-
-class FlutterCookieManager implements MethodCallHandler {
-  private final MethodChannel methodChannel;
-
-  FlutterCookieManager(BinaryMessenger messenger) {
-    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
-    methodChannel.setMethodCallHandler(this);
-  }
-
-  @Override
-  public void onMethodCall(MethodCall methodCall, Result result) {
-    switch (methodCall.method) {
-      case "clearCookies":
-        clearCookies(result);
-        break;
-      default:
-        result.notImplemented();
-    }
-  }
-
-  void dispose() {
-    methodChannel.setMethodCallHandler(null);
-  }
-
-  private static void clearCookies(final Result result) {
-    CookieManager cookieManager = CookieManager.getInstance();
-    final boolean hasCookies = cookieManager.hasCookies();
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
-      cookieManager.removeAllCookies(
-          new ValueCallback<Boolean>() {
-            @Override
-            public void onReceiveValue(Boolean value) {
-              result.success(hasCookies);
-            }
-          });
-    } else {
-      cookieManager.removeAllCookie();
-      result.success(hasCookies);
-    }
-  }
-}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index 0e6776e..8ef0b8d 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
@@ -162,6 +162,94 @@
     void error(Throwable error);
   }
 
+  private static class CookieManagerHostApiCodec extends StandardMessageCodec {
+    public static final CookieManagerHostApiCodec INSTANCE = new CookieManagerHostApiCodec();
+
+    private CookieManagerHostApiCodec() {}
+  }
+
+  /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+  public interface CookieManagerHostApi {
+    void clearCookies(Result<Boolean> result);
+
+    void setCookie(String url, String value);
+
+    /** The codec used by CookieManagerHostApi. */
+    static MessageCodec<Object> getCodec() {
+      return CookieManagerHostApiCodec.INSTANCE;
+    }
+
+    /**
+     * Sets up an instance of `CookieManagerHostApi` to handle messages through the
+     * `binaryMessenger`.
+     */
+    static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) {
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.CookieManagerHostApi.clearCookies",
+                getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  Result<Boolean> resultCallback =
+                      new Result<Boolean>() {
+                        public void success(Boolean result) {
+                          wrapped.put("result", result);
+                          reply.reply(wrapped);
+                        }
+
+                        public void error(Throwable error) {
+                          wrapped.put("error", wrapError(error));
+                          reply.reply(wrapped);
+                        }
+                      };
+
+                  api.clearCookies(resultCallback);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                  reply.reply(wrapped);
+                }
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  String urlArg = (String) args.get(0);
+                  if (urlArg == null) {
+                    throw new NullPointerException("urlArg unexpectedly null.");
+                  }
+                  String valueArg = (String) args.get(1);
+                  if (valueArg == null) {
+                    throw new NullPointerException("valueArg unexpectedly null.");
+                  }
+                  api.setCookie(urlArg, valueArg);
+                  wrapped.put("result", null);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+    }
+  }
+
   private static class WebViewHostApiCodec extends StandardMessageCodec {
     public static final WebViewHostApiCodec INSTANCE = new WebViewHostApiCodec();
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
index cbeda8d..4ef622f 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java
@@ -13,6 +13,7 @@
 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.platform.PlatformViewRegistry;
+import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi;
 import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi;
@@ -30,7 +31,6 @@
  */
 public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
   private FlutterPluginBinding pluginBinding;
-  private FlutterCookieManager flutterCookieManager;
   private WebViewHostApiImpl webViewHostApi;
   private JavaScriptChannelHostApiImpl javaScriptChannelHostApi;
 
@@ -65,7 +65,6 @@
             registrar.view(),
             new FlutterAssetManager.RegistrarFlutterAssetManager(
                 registrar.context().getAssets(), registrar));
-    new FlutterCookieManager(registrar.messenger());
   }
 
   private void setUp(
@@ -74,7 +73,6 @@
       Context context,
       View containerView,
       FlutterAssetManager flutterAssetManager) {
-    new FlutterCookieManager(binaryMessenger);
 
     InstanceManager instanceManager = new InstanceManager();
 
@@ -117,6 +115,7 @@
             instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator()));
     FlutterAssetManagerHostApi.setup(
         binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager));
+    CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl());
   }
 
   @Override
@@ -132,14 +131,7 @@
   }
 
   @Override
-  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
-    if (flutterCookieManager == null) {
-      return;
-    }
-
-    flutterCookieManager.dispose();
-    flutterCookieManager = null;
-  }
+  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
 
   @Override
   public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java
new file mode 100644
index 0000000..6daeb1b
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java
@@ -0,0 +1,83 @@
+// Copyright 2013 The Flutter 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.webviewflutter;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Build;
+import android.webkit.CookieManager;
+import android.webkit.ValueCallback;
+import io.flutter.plugins.webviewflutter.utils.TestUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+
+public class CookieManagerHostApiImplTest {
+
+  private CookieManager cookieManager;
+  private MockedStatic<CookieManager> staticMockCookieManager;
+
+  @Before
+  public void setup() {
+    staticMockCookieManager = mockStatic(CookieManager.class);
+    cookieManager = mock(CookieManager.class);
+    when(CookieManager.getInstance()).thenReturn(cookieManager);
+    when(cookieManager.hasCookies()).thenReturn(true);
+    doAnswer(
+            answer -> {
+              ((ValueCallback<Boolean>) answer.getArgument(0)).onReceiveValue(true);
+              return null;
+            })
+        .when(cookieManager)
+        .removeAllCookies(any());
+  }
+
+  @After
+  public void tearDown() {
+    staticMockCookieManager.close();
+  }
+
+  @Test
+  public void setCookieShouldCallSetCookie() {
+    // Setup
+    CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
+    // Run
+    impl.setCookie("flutter.dev", "foo=bar; path=/");
+    // Verify
+    verify(cookieManager).setCookie("flutter.dev", "foo=bar; path=/");
+  }
+
+  @Test
+  public void clearCookiesShouldCallRemoveAllCookiesOnAndroidLAbove() {
+    // Setup
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP);
+    GeneratedAndroidWebView.Result<Boolean> result = mock(GeneratedAndroidWebView.Result.class);
+    CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
+    // Run
+    impl.clearCookies(result);
+    // Verify
+    verify(cookieManager).removeAllCookies(any());
+    verify(result).success(true);
+  }
+
+  @Test
+  public void clearCookiesShouldCallRemoveAllCookieBelowAndroidL() {
+    // Setup
+    TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.KITKAT_WATCH);
+    GeneratedAndroidWebView.Result<Boolean> result = mock(GeneratedAndroidWebView.Result.class);
+    CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl();
+    // Run
+    impl.clearCookies(result);
+    // Verify
+    verify(cookieManager).removeAllCookie();
+    verify(result).success(true);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java
new file mode 100644
index 0000000..31e7d58
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java
@@ -0,0 +1,47 @@
+// Copyright 2013 The Flutter 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.webviewflutter.utils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import org.junit.Assert;
+
+public class TestUtils {
+  public static <T> void setFinalStatic(Class<T> classToModify, String fieldName, Object newValue) {
+    try {
+      Field field = classToModify.getField(fieldName);
+      field.setAccessible(true);
+
+      Field modifiersField = Field.class.getDeclaredField("modifiers");
+      modifiersField.setAccessible(true);
+      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+
+      field.set(null, newValue);
+    } catch (Exception e) {
+      Assert.fail("Unable to mock static field: " + fieldName);
+    }
+  }
+
+  public static <T> void setPrivateField(T instance, String fieldName, Object newValue) {
+    try {
+      Field field = instance.getClass().getDeclaredField(fieldName);
+      field.setAccessible(true);
+      field.set(instance, newValue);
+    } catch (Exception e) {
+      Assert.fail("Unable to mock private field: " + fieldName);
+    }
+  }
+
+  public static <T> Object getPrivateField(T instance, String fieldName) {
+    try {
+      Field field = instance.getClass().getDeclaredField(fieldName);
+      field.setAccessible(true);
+      return field.get(instance);
+    } catch (Exception e) {
+      Assert.fail("Unable to mock private field: " + fieldName);
+      return null;
+    }
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
index eb3162a..0d0cd59 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -191,6 +191,7 @@
   loadHtmlString,
   transparentBackground,
   doPostRequest,
+  setCookie,
 }
 
 class _SampleMenu extends StatelessWidget {
@@ -244,6 +245,9 @@
               case _MenuOptions.doPostRequest:
                 _onDoPostRequest(controller.data!, context);
                 break;
+              case _MenuOptions.setCookie:
+                _onSetCookie(controller.data!, context);
+                break;
             }
           },
           itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -297,6 +301,10 @@
               value: _MenuOptions.doPostRequest,
               child: Text('Post Request'),
             ),
+            const PopupMenuItem<_MenuOptions>(
+              value: _MenuOptions.setCookie,
+              child: Text('Set Cookie'),
+            ),
           ],
         );
       },
@@ -356,7 +364,7 @@
 
   Future<void> _onClearCookies(
       WebViewController controller, BuildContext context) async {
-    final bool hadCookies = await WebView.platform.clearCookies();
+    final bool hadCookies = await WebViewCookieManager.instance.clearCookies();
     String message = 'There were cookies. Now, they are gone!';
     if (!hadCookies) {
       message = 'There are no cookies.';
@@ -367,6 +375,15 @@
     ));
   }
 
+  Future<void> _onSetCookie(
+      WebViewController controller, BuildContext context) async {
+    await WebViewCookieManager.instance.setCookie(
+      const WebViewCookie(
+          name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'),
+    );
+    await controller.loadUrl('https://httpbin.org/anything');
+  }
+
   Future<void> _onNavigationDelegateExample(
       WebViewController controller, BuildContext context) async {
     final String contentBase64 =
diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
index adaf7fb..91ea663 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart
@@ -3,11 +3,13 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:webview_flutter_android/webview_android.dart';
+import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
 
 import 'navigation_decision.dart';
@@ -61,6 +63,7 @@
     Key? key,
     this.onWebViewCreated,
     this.initialUrl,
+    this.initialCookies = const <WebViewCookie>[],
     this.javascriptMode = JavascriptMode.disabled,
     this.javascriptChannels,
     this.navigationDelegate,
@@ -104,6 +107,9 @@
   /// The initial URL to load.
   final String? initialUrl;
 
+  /// The initial cookies to set.
+  final List<WebViewCookie> initialCookies;
+
   /// Whether JavaScript execution is enabled.
   final JavascriptMode javascriptMode;
 
@@ -295,6 +301,7 @@
         autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
         userAgent: widget.userAgent,
         backgroundColor: widget.backgroundColor,
+        cookies: widget.initialCookies,
       ),
       javascriptChannelRegistry: _javascriptChannelRegistry,
     );
@@ -674,3 +681,21 @@
     zoomEnabled: widget.zoomEnabled,
   );
 }
+
+/// App-facing cookie manager that exposes the correct platform implementation.
+class WebViewCookieManager extends WebViewCookieManagerPlatform {
+  WebViewCookieManager._();
+
+  /// Returns an instance of the cookie manager for the current platform.
+  static WebViewCookieManagerPlatform get instance {
+    if (WebViewCookieManagerPlatform.instance == null) {
+      if (Platform.isAndroid) {
+        WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager();
+      } else {
+        throw AssertionError(
+            'This platform is currently unsupported for webview_flutter_android.');
+      }
+    }
+    return WebViewCookieManagerPlatform.instance!;
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index d027016..dfa05cd 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -375,6 +375,49 @@
   }
 }
 
+/// Manages cookies globally for all webviews.
+class CookieManager {
+  CookieManager._();
+
+  static CookieManager? _instance;
+
+  /// Gets the globally set CookieManager instance.
+  static CookieManager get instance => _instance ??= CookieManager._();
+
+  /// Setter for the singleton value, for testing purposes only.
+  @visibleForTesting
+  static set instance(CookieManager value) => _instance = value;
+
+  /// Pigeon Host Api implementation for [CookieManager].
+  @visibleForTesting
+  static CookieManagerHostApi api = CookieManagerHostApi();
+
+  /// Sets a single cookie (key-value pair) for the given URL. Any existing
+  /// cookie with the same host, path and name will be replaced with the new
+  /// cookie. The cookie being set will be ignored if it is expired. To set
+  /// multiple cookies, your application should invoke this method multiple
+  /// times.
+  ///
+  /// The value parameter must follow the format of the Set-Cookie HTTP
+  /// response header defined by RFC6265bis. This is a key-value pair of the
+  /// form "key=value", optionally followed by a list of cookie attributes
+  /// delimited with semicolons (ex. "key=value; Max-Age=123"). Please consult
+  /// the RFC specification for a list of valid attributes.
+  ///
+  /// Note: if specifying a value containing the "Secure" attribute, url must
+  /// use the "https://" scheme.
+  ///
+  /// Params:
+  /// url – the URL for which the cookie is to be set
+  /// value – the cookie as a string, using the format of the 'Set-Cookie' HTTP response header
+  Future<void> setCookie(String url, String value) => api.setCookie(url, value);
+
+  /// Removes all cookies.
+  ///
+  /// The returned future resolves to true if any cookies were removed.
+  Future<bool> clearCookies() => api.clearCookies();
+}
+
 /// Manages settings state for a [WebView].
 ///
 /// When a WebView is first created, it obtains a set of default settings. These
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
index e0a1db3..810a717 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -63,6 +63,72 @@
   }
 }
 
+class _CookieManagerHostApiCodec extends StandardMessageCodec {
+  const _CookieManagerHostApiCodec();
+}
+
+class CookieManagerHostApi {
+  /// Constructor for [CookieManagerHostApi].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  CookieManagerHostApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _CookieManagerHostApiCodec();
+
+  Future<bool> clearCookies() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as bool?)!;
+    }
+  }
+
+  Future<void> setCookie(String arg_url, String arg_value) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap = await channel
+        .send(<Object>[arg_url, arg_value]) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return;
+    }
+  }
+}
+
 class _WebViewHostApiCodec extends StandardMessageCodec {
   const _WebViewHostApiCodec();
 }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart
index ee474b9..1f0eb7b 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android.dart
@@ -65,5 +65,11 @@
   }
 
   @override
-  Future<bool> clearCookies() => MethodChannelWebViewPlatform.clearCookies();
+  Future<bool> clearCookies() {
+    if (WebViewCookieManagerPlatform.instance == null) {
+      throw Exception(
+          'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.');
+    }
+    return WebViewCookieManagerPlatform.instance!.clearCookies();
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart
new file mode 100644
index 0000000..bba75ff
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_cookie_manager.dart
@@ -0,0 +1,36 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:webview_flutter_android/src/android_webview.dart'
+    as android_webview;
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+
+/// Handles all cookie operations for the current platform.
+class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform {
+  @override
+  Future<bool> clearCookies() =>
+      android_webview.CookieManager.instance.clearCookies();
+
+  @override
+  Future<void> setCookie(WebViewCookie cookie) {
+    if (!_isValidPath(cookie.path)) {
+      throw ArgumentError(
+          'The path property for the provided cookie was not given a legal value.');
+    }
+    return android_webview.CookieManager.instance.setCookie(
+      cookie.domain,
+      '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}',
+    );
+  }
+
+  bool _isValidPath(String path) {
+    // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1
+    for (final int char in path.codeUnits) {
+      if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index fc295be..1dec9c1 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -6,6 +6,7 @@
 import 'dart:typed_data';
 
 import 'package:flutter/widgets.dart';
+import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
 import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
 
 import 'src/android_webview.dart' as android_webview;
@@ -363,6 +364,14 @@
     }
 
     addJavascriptChannels(creationParams.javascriptChannelNames);
+
+    // TODO(BeMacized): Remove once platform implementations
+    // are able to register themselves (Flutter >=2.8),
+    // https://github.com/flutter/flutter/issues/94224
+    WebViewCookieManagerPlatform.instance ??= WebViewAndroidCookieManager();
+
+    creationParams.cookies
+        .forEach(WebViewCookieManagerPlatform.instance!.setCookie);
   }
 
   Future<void> _setHasProgressTracking(bool hasProgressTracking) async {
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
index 4907dad..36862f7 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -18,6 +18,14 @@
   String? description;
 }
 
+@HostApi()
+abstract class CookieManagerHostApi {
+  @async
+  bool clearCookies();
+
+  void setCookie(String url, String value);
+}
+
 @HostApi(dartHostTestHandler: 'TestWebViewHostApi')
 abstract class WebViewHostApi {
   void create(int instanceId, bool useHybridComposition);
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 08bcbb5..34bea57 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.7.0
+version: 2.8.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index b903678..cc29fc7 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -14,6 +14,7 @@
 import 'android_webview_test.mocks.dart';
 
 @GenerateMocks(<Type>[
+  CookieManagerHostApi,
   DownloadListener,
   JavaScriptChannel,
   TestDownloadListenerHostApi,
@@ -647,4 +648,20 @@
       });
     });
   });
+
+  group('CookieManager', () {
+    test('setCookie calls setCookie on CookieManagerHostApi', () {
+      CookieManager.api = MockCookieManagerHostApi();
+      CookieManager.instance.setCookie('foo', 'bar');
+      verify(CookieManager.api.setCookie('foo', 'bar'));
+    });
+
+    test('clearCookies calls clearCookies on CookieManagerHostApi', () {
+      CookieManager.api = MockCookieManagerHostApi();
+      when(CookieManager.api.clearCookies())
+          .thenAnswer((_) => Future<bool>.value(true));
+      CookieManager.instance.clearCookies();
+      verify(CookieManager.api.clearCookies());
+    });
+  });
 }
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index 8bb5694..d25d233 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -6,14 +6,15 @@
 // in webview_flutter_android/test/android_webview_test.dart.
 // Do not manually edit this file.
 
-import 'dart:async' as _i5;
-import 'dart:typed_data' as _i4;
-import 'dart:ui' as _i6;
+import 'dart:async' as _i4;
+import 'dart:typed_data' as _i6;
+import 'dart:ui' as _i7;
 
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:webview_flutter_android/src/android_webview.dart' as _i2;
+import 'package:webview_flutter_android/src/android_webview.pigeon.dart' as _i3;
 
-import 'android_webview.pigeon.dart' as _i3;
+import 'android_webview.pigeon.dart' as _i5;
 
 // ignore_for_file: avoid_redundant_argument_values
 // ignore_for_file: avoid_setters_without_getters
@@ -26,6 +27,28 @@
 
 class _FakeWebSettings_0 extends _i1.Fake implements _i2.WebSettings {}
 
+/// A class which mocks [CookieManagerHostApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockCookieManagerHostApi extends _i1.Mock
+    implements _i3.CookieManagerHostApi {
+  MockCookieManagerHostApi() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i4.Future<bool> clearCookies() =>
+      (super.noSuchMethod(Invocation.method(#clearCookies, []),
+          returnValue: Future<bool>.value(false)) as _i4.Future<bool>);
+  @override
+  _i4.Future<void> setCookie(String? arg_url, String? arg_value) =>
+      (super.noSuchMethod(Invocation.method(#setCookie, [arg_url, arg_value]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+  @override
+  String toString() => super.toString();
+}
+
 /// A class which mocks [DownloadListener].
 ///
 /// See the documentation for Mockito's code generation for more information.
@@ -69,7 +92,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestDownloadListenerHostApi extends _i1.Mock
-    implements _i3.TestDownloadListenerHostApi {
+    implements _i5.TestDownloadListenerHostApi {
   MockTestDownloadListenerHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -86,7 +109,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestJavaScriptChannelHostApi extends _i1.Mock
-    implements _i3.TestJavaScriptChannelHostApi {
+    implements _i5.TestJavaScriptChannelHostApi {
   MockTestJavaScriptChannelHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -103,7 +126,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestWebChromeClientHostApi extends _i1.Mock
-    implements _i3.TestWebChromeClientHostApi {
+    implements _i5.TestWebChromeClientHostApi {
   MockTestWebChromeClientHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -121,7 +144,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestWebSettingsHostApi extends _i1.Mock
-    implements _i3.TestWebSettingsHostApi {
+    implements _i5.TestWebSettingsHostApi {
   MockTestWebSettingsHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -195,7 +218,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestWebViewClientHostApi extends _i1.Mock
-    implements _i3.TestWebViewClientHostApi {
+    implements _i5.TestWebViewClientHostApi {
   MockTestWebViewClientHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -213,7 +236,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestWebViewHostApi extends _i1.Mock
-    implements _i3.TestWebViewHostApi {
+    implements _i5.TestWebViewHostApi {
   MockTestWebViewHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -246,7 +269,7 @@
           Invocation.method(#loadUrl, [instanceId, url, headers]),
           returnValueForMissingStub: null);
   @override
-  void postUrl(int? instanceId, String? url, _i4.Uint8List? data) =>
+  void postUrl(int? instanceId, String? url, _i6.Uint8List? data) =>
       super.noSuchMethod(Invocation.method(#postUrl, [instanceId, url, data]),
           returnValueForMissingStub: null);
   @override
@@ -279,12 +302,12 @@
           Invocation.method(#clearCache, [instanceId, includeDiskFiles]),
           returnValueForMissingStub: null);
   @override
-  _i5.Future<String> evaluateJavascript(
+  _i4.Future<String> evaluateJavascript(
           int? instanceId, String? javascriptString) =>
       (super.noSuchMethod(
           Invocation.method(
               #evaluateJavascript, [instanceId, javascriptString]),
-          returnValue: Future<String>.value('')) as _i5.Future<String>);
+          returnValue: Future<String>.value('')) as _i4.Future<String>);
   @override
   String getTitle(int? instanceId) =>
       (super.noSuchMethod(Invocation.method(#getTitle, [instanceId]),
@@ -353,7 +376,7 @@
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockTestAssetManagerHostApi extends _i1.Mock
-    implements _i3.TestAssetManagerHostApi {
+    implements _i5.TestAssetManagerHostApi {
   MockTestAssetManagerHostApi() {
     _i1.throwOnMissingStub(this);
   }
@@ -403,15 +426,15 @@
       (super.noSuchMethod(Invocation.getter(#settings),
           returnValue: _FakeWebSettings_0()) as _i2.WebSettings);
   @override
-  _i5.Future<void> loadData(
+  _i4.Future<void> loadData(
           {String? data, String? mimeType, String? encoding}) =>
       (super.noSuchMethod(
           Invocation.method(#loadData, [],
               {#data: data, #mimeType: mimeType, #encoding: encoding}),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> loadDataWithBaseUrl(
+  _i4.Future<void> loadDataWithBaseUrl(
           {String? baseUrl,
           String? data,
           String? mimeType,
@@ -426,114 +449,114 @@
             #historyUrl: historyUrl
           }),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> loadUrl(String? url, Map<String, String>? headers) =>
+  _i4.Future<void> loadUrl(String? url, Map<String, String>? headers) =>
       (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> postUrl(String? url, _i4.Uint8List? data) =>
+  _i4.Future<void> postUrl(String? url, _i6.Uint8List? data) =>
       (super.noSuchMethod(Invocation.method(#postUrl, [url, data]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<String?> getUrl() =>
+  _i4.Future<String?> getUrl() =>
       (super.noSuchMethod(Invocation.method(#getUrl, []),
-          returnValue: Future<String?>.value()) as _i5.Future<String?>);
+          returnValue: Future<String?>.value()) as _i4.Future<String?>);
   @override
-  _i5.Future<bool> canGoBack() =>
+  _i4.Future<bool> canGoBack() =>
       (super.noSuchMethod(Invocation.method(#canGoBack, []),
-          returnValue: Future<bool>.value(false)) as _i5.Future<bool>);
+          returnValue: Future<bool>.value(false)) as _i4.Future<bool>);
   @override
-  _i5.Future<bool> canGoForward() =>
+  _i4.Future<bool> canGoForward() =>
       (super.noSuchMethod(Invocation.method(#canGoForward, []),
-          returnValue: Future<bool>.value(false)) as _i5.Future<bool>);
+          returnValue: Future<bool>.value(false)) as _i4.Future<bool>);
   @override
-  _i5.Future<void> goBack() =>
+  _i4.Future<void> goBack() =>
       (super.noSuchMethod(Invocation.method(#goBack, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> goForward() =>
+  _i4.Future<void> goForward() =>
       (super.noSuchMethod(Invocation.method(#goForward, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> reload() =>
+  _i4.Future<void> reload() =>
       (super.noSuchMethod(Invocation.method(#reload, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> clearCache(bool? includeDiskFiles) =>
+  _i4.Future<void> clearCache(bool? includeDiskFiles) =>
       (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<String?> evaluateJavascript(String? javascriptString) => (super
+  _i4.Future<String?> evaluateJavascript(String? javascriptString) => (super
       .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]),
-          returnValue: Future<String?>.value()) as _i5.Future<String?>);
+          returnValue: Future<String?>.value()) as _i4.Future<String?>);
   @override
-  _i5.Future<String?> getTitle() =>
+  _i4.Future<String?> getTitle() =>
       (super.noSuchMethod(Invocation.method(#getTitle, []),
-          returnValue: Future<String?>.value()) as _i5.Future<String?>);
+          returnValue: Future<String?>.value()) as _i4.Future<String?>);
   @override
-  _i5.Future<void> scrollTo(int? x, int? y) =>
+  _i4.Future<void> scrollTo(int? x, int? y) =>
       (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> scrollBy(int? x, int? y) =>
+  _i4.Future<void> scrollBy(int? x, int? y) =>
       (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<int> getScrollX() =>
+  _i4.Future<int> getScrollX() =>
       (super.noSuchMethod(Invocation.method(#getScrollX, []),
-          returnValue: Future<int>.value(0)) as _i5.Future<int>);
+          returnValue: Future<int>.value(0)) as _i4.Future<int>);
   @override
-  _i5.Future<int> getScrollY() =>
+  _i4.Future<int> getScrollY() =>
       (super.noSuchMethod(Invocation.method(#getScrollY, []),
-          returnValue: Future<int>.value(0)) as _i5.Future<int>);
+          returnValue: Future<int>.value(0)) as _i4.Future<int>);
   @override
-  _i5.Future<void> setWebViewClient(_i2.WebViewClient? webViewClient) =>
+  _i4.Future<void> setWebViewClient(_i2.WebViewClient? webViewClient) =>
       (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> addJavaScriptChannel(
+  _i4.Future<void> addJavaScriptChannel(
           _i2.JavaScriptChannel? javaScriptChannel) =>
       (super.noSuchMethod(
           Invocation.method(#addJavaScriptChannel, [javaScriptChannel]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> removeJavaScriptChannel(
+  _i4.Future<void> removeJavaScriptChannel(
           _i2.JavaScriptChannel? javaScriptChannel) =>
       (super.noSuchMethod(
           Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> setDownloadListener(_i2.DownloadListener? listener) =>
+  _i4.Future<void> setDownloadListener(_i2.DownloadListener? listener) =>
       (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> setWebChromeClient(_i2.WebChromeClient? client) =>
+  _i4.Future<void> setWebChromeClient(_i2.WebChromeClient? client) =>
       (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> setBackgroundColor(_i6.Color? color) =>
+  _i4.Future<void> setBackgroundColor(_i7.Color? color) =>
       (super.noSuchMethod(Invocation.method(#setBackgroundColor, [color]),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
-  _i5.Future<void> release() =>
+  _i4.Future<void> release() =>
       (super.noSuchMethod(Invocation.method(#release, []),
           returnValue: Future<void>.value(),
-          returnValueForMissingStub: Future<void>.value()) as _i5.Future<void>);
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
   String toString() => super.toString();
 }
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart
new file mode 100644
index 0000000..4f274ff
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.dart
@@ -0,0 +1,53 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:webview_flutter_android/src/android_webview.dart'
+    as android_webview;
+import 'package:webview_flutter_android/webview_android_cookie_manager.dart';
+import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+
+import 'webview_android_cookie_manager_test.mocks.dart';
+
+@GenerateMocks(<Type>[android_webview.CookieManager])
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  setUp(() {
+    android_webview.CookieManager.instance = MockCookieManager();
+  });
+
+  test('clearCookies should call android_webview.clearCookies', () {
+    when(android_webview.CookieManager.instance.clearCookies())
+        .thenAnswer((_) => Future<bool>.value(true));
+    WebViewAndroidCookieManager().clearCookies();
+    verify(android_webview.CookieManager.instance.clearCookies());
+  });
+
+  test('setCookie should throw ArgumentError for cookie with invalid path', () {
+    expect(
+      () => WebViewAndroidCookieManager().setCookie(const WebViewCookie(
+        name: 'foo',
+        value: 'bar',
+        domain: 'flutter.dev',
+        path: 'invalid;path',
+      )),
+      throwsA(const TypeMatcher<ArgumentError>()),
+    );
+  });
+
+  test(
+      'setCookie should call android_webview.csetCookie with properly formatted cookie value',
+      () {
+    WebViewAndroidCookieManager().setCookie(const WebViewCookie(
+      name: 'foo&',
+      value: 'bar@',
+      domain: 'flutter.dev',
+    ));
+    verify(android_webview.CookieManager.instance
+        .setCookie('flutter.dev', 'foo%26=bar%40; path=/'));
+  });
+}
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart
new file mode 100644
index 0000000..131977b
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_cookie_manager_test.mocks.dart
@@ -0,0 +1,38 @@
+// Mocks generated by Mockito 5.0.16 from annotations
+// in webview_flutter_android/test/webview_android_cookie_manager_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i3;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:webview_flutter_android/src/android_webview.dart' as _i2;
+
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+
+/// A class which mocks [CookieManager].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockCookieManager extends _i1.Mock implements _i2.CookieManager {
+  MockCookieManager() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i3.Future<void> setCookie(String? url, String? value) =>
+      (super.noSuchMethod(Invocation.method(#setCookie, [url, value]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i3.Future<void>);
+  @override
+  _i3.Future<bool> clearCookies() =>
+      (super.noSuchMethod(Invocation.method(#clearCookies, []),
+          returnValue: Future<bool>.value(false)) as _i3.Future<bool>);
+  @override
+  String toString() => super.toString();
+}