From 2d0774654483028fc04be124005d57d30de56f34 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 18:42:15 +0900 Subject: [PATCH 01/20] Add wrapper for androidx.webkit.WebSettingsCompat --- .../webviewflutter/AndroidWebkitLibrary.g.kt | 87 +++++++++++++++++++ .../webviewflutter/ProxyApiRegistrar.java | 6 ++ .../pigeons/android_webkit.dart | 13 +++ 3 files changed, 106 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt index 58fc0ed2924..e9a91d3cd8b 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt @@ -567,6 +567,12 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: */ abstract fun getPigeonApiCertificate(): PigeonApiCertificate + /** + * An implementation of [PigeonApiWebSettingsCompat] used to add a new Dart instance of + * `WebSettingsCompat` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebSettingsCompat(): PigeonApiWebSettingsCompat + fun setUp() { AndroidWebkitLibraryPigeonInstanceManagerApi.setUpMessageHandlers( binaryMessenger, instanceManager) @@ -598,6 +604,8 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: binaryMessenger, getPigeonApiSslCertificateDName()) PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiSslCertificate()) PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiCertificate()) + PigeonApiWebSettingsCompat.setUpMessageHandlers( + binaryMessenger, getPigeonApiWebSettingsCompat()) } fun tearDown() { @@ -623,6 +631,7 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: PigeonApiSslCertificateDName.setUpMessageHandlers(binaryMessenger, null) PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, null) PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebSettingsCompat.setUpMessageHandlers(binaryMessenger, null) } } @@ -727,6 +736,8 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec( registrar.getPigeonApiSslCertificate().pigeon_newInstance(value) {} } else if (value is java.security.cert.Certificate) { registrar.getPigeonApiCertificate().pigeon_newInstance(value) {} + } else if (value is androidx.webkit.WebSettingsCompat) { + registrar.getPigeonApiWebSettingsCompat().pigeon_newInstance(value) {} } when { @@ -6310,3 +6321,79 @@ abstract class PigeonApiCertificate( } } } + +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebSettingsCompat( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun setPaymentRequestEnabled(webSettings: android.webkit.WebSettings, enabled: Boolean) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettingsCompat?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setPaymentRequestEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val webSettingsArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setPaymentRequestEnabled(webSettingsArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebSettingsCompat and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.webkit.WebSettingsCompat, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} + diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java index 671ecf425f4..420e5daa582 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java @@ -251,4 +251,10 @@ public void setContext(@NonNull Context context) { public FlutterAssetManager getFlutterAssetManager() { return flutterAssetManager; } + + @NonNull + @Override + public PigeonApiWebSettingsCompat getPigeonApiWebSettingsCompat() { + return new WebSettingsCompatProxyApi(this); + } } diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart index e8041c59abe..750413f07dd 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -1091,3 +1091,16 @@ abstract class Certificate { /// The encoded form of this certificate. Uint8List getEncoded(); } + +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.webkit.WebSettingsCompat', + ), +) +abstract class WebSettingsCompat { + @static + void setPaymentRequestEnabled( + WebSettings webSettings, + bool enabled, + ); +} From 1a01f138ac3075b599cd243de53969db85b29b50 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 18:50:21 +0900 Subject: [PATCH 02/20] Add wrapper for androidx.webkit.WebViewFeature --- .../webviewflutter/AndroidWebkitLibrary.g.kt | 82 +++++++++++++++++++ .../webviewflutter/ProxyApiRegistrar.java | 6 ++ .../pigeons/android_webkit.dart | 10 +++ 3 files changed, 98 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt index e9a91d3cd8b..c0dad62a75d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt @@ -573,6 +573,12 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: */ abstract fun getPigeonApiWebSettingsCompat(): PigeonApiWebSettingsCompat + /** + * An implementation of [PigeonApiWebViewFeature] used to add a new Dart instance of + * `WebViewFeature` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebViewFeature(): PigeonApiWebViewFeature + fun setUp() { AndroidWebkitLibraryPigeonInstanceManagerApi.setUpMessageHandlers( binaryMessenger, instanceManager) @@ -606,6 +612,7 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiCertificate()) PigeonApiWebSettingsCompat.setUpMessageHandlers( binaryMessenger, getPigeonApiWebSettingsCompat()) + PigeonApiWebViewFeature.setUpMessageHandlers(binaryMessenger, getPigeonApiWebViewFeature()) } fun tearDown() { @@ -632,6 +639,7 @@ abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, null) PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, null) PigeonApiWebSettingsCompat.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebViewFeature.setUpMessageHandlers(binaryMessenger, null) } } @@ -738,6 +746,8 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec( registrar.getPigeonApiCertificate().pigeon_newInstance(value) {} } else if (value is androidx.webkit.WebSettingsCompat) { registrar.getPigeonApiWebSettingsCompat().pigeon_newInstance(value) {} + } else if (value is androidx.webkit.WebViewFeature) { + registrar.getPigeonApiWebViewFeature().pigeon_newInstance(value) {} } when { @@ -6397,3 +6407,75 @@ abstract class PigeonApiWebSettingsCompat( } } +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebViewFeature( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun isFeatureSupported(feature: String): Boolean + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebViewFeature?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebViewFeature.isFeatureSupported", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featureArg = args[0] as String + val wrapped: List = + try { + listOf(api.isFeatureSupported(featureArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebViewFeature and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.webkit.WebViewFeature, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java index 420e5daa582..84e025a7a0c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java @@ -252,6 +252,12 @@ public FlutterAssetManager getFlutterAssetManager() { return flutterAssetManager; } + @NonNull + @Override + public PigeonApiWebViewFeature getPigeonApiWebViewFeature() { + return new WebViewFeatureProxyApi(this); + } + @NonNull @Override public PigeonApiWebSettingsCompat getPigeonApiWebSettingsCompat() { diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart index 750413f07dd..4eaf1740930 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -1104,3 +1104,13 @@ abstract class WebSettingsCompat { bool enabled, ); } + +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.webkit.WebViewFeature', + ), +) +abstract class WebViewFeature { + @static + bool isFeatureSupported(String feature); +} From d098b472e3e20e17316c0b31c04a769793b4a088 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 18:55:22 +0900 Subject: [PATCH 03/20] Expose wrappers --- .../WebSettingsCompatProxyApi.java | 25 ++ .../WebViewFeatureProxyApi.java | 24 ++ .../lib/src/android_proxy.dart | 8 + .../lib/src/android_webkit.g.dart | 223 ++++++++++++++++++ .../lib/src/android_webkit_constants.dart | 11 + .../lib/src/android_webview_controller.dart | 34 +++ 6 files changed, 325 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java new file mode 100644 index 00000000000..19e42ba5afd --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -0,0 +1,25 @@ +// 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.webkit.WebSettings; +import androidx.annotation.NonNull; +import androidx.webkit.WebSettingsCompat; + +/** + * Host api implementation for {@link WebSettingsCompat}. + * + *

Handles static methods for {@link WebSettingsCompat}. + */ +public class WebSettingsCompatProxyApi extends PigeonApiWebSettingsCompat { + public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) { + WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); + } +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java new file mode 100644 index 00000000000..954bdb400cb --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java @@ -0,0 +1,24 @@ +// 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 androidx.annotation.NonNull; +import androidx.webkit.WebViewFeature; + +/** + * Host api implementation for {@link WebViewFeature}. + * + *

Handles creating {@link WebViewFeature}s that intercommunicate with a paired Dart object. + */ +public class WebViewFeatureProxyApi extends PigeonApiWebViewFeature { + public WebViewFeatureProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public boolean isFeatureSupported(@NonNull String feature) { + return WebViewFeature.isFeatureSupported(feature); + } +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart index e32520c358b..12c460f4129 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -26,6 +26,8 @@ class AndroidWebViewProxy { this.instanceCookieManager = _instanceCookieManager, this.instanceFlutterAssetManager = _instanceFlutterAssetManager, this.instanceWebStorage = _instanceWebStorage, + this.isWebViewFeatureSupported = WebViewFeature.isFeatureSupported, + this.setPaymentRequestEnabled = WebSettingsCompat.setPaymentRequestEnabled, }); /// Constructs [WebView]. @@ -168,6 +170,12 @@ class AndroidWebViewProxy { /// Calls to [WebStorage.instance]. final WebStorage Function() instanceWebStorage; + /// Calls to [WebViewFeature.isFeatureSupported]. + final Future Function(String) isWebViewFeatureSupported; + + /// Calls to [WebSettingsCompat.setPaymentRequestEnabled]. + final Future Function(WebSettings, bool) setPaymentRequestEnabled; + static CookieManager _instanceCookieManager() => CookieManager.instance; static FlutterAssetManager _instanceFlutterAssetManager() => diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart index 2675271ce50..3dbef1ed227 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart @@ -196,6 +196,10 @@ class PigeonInstanceManager { pigeon_instanceManager: instanceManager); Certificate.pigeon_setUpMessageHandlers( pigeon_instanceManager: instanceManager); + WebSettingsCompat.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); + WebViewFeature.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager); return instanceManager; } @@ -8349,3 +8353,222 @@ class Certificate extends PigeonInternalProxyApiBaseClass { ); } } + +class WebSettingsCompat extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebSettingsCompat] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebSettingsCompat.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecWebSettingsCompat = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebSettingsCompat Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebSettingsCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + static Future setPaymentRequestEnabled( + WebSettings webSettings, + bool enabled, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setPaymentRequestEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([webSettings, enabled]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebSettingsCompat pigeon_copy() { + return WebSettingsCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +class WebViewFeature extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebViewFeature] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebViewFeature.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebViewFeature = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebViewFeature Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance was null.'); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert(arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance was null, expected non-null int.'); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebViewFeature.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } + + static Future isFeatureSupported( + String feature, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance); + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebViewFeature.isFeatureSupported'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([feature]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + @override + WebViewFeature pigeon_copy() { + return WebViewFeature.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit_constants.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit_constants.dart index 81cd06c7176..2d962ccd2f1 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit_constants.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit_constants.dart @@ -117,3 +117,14 @@ class WebViewClientConstants { /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSUPPORTED_SCHEME static const int errorUnsupportedScheme = -10; } + +/// Class constants for [WebViewFeature]. +/// +/// Since the Dart [WebViewFeature] is generated, the constants for the class +/// are added here. +class WebViewFeatureConstants { + /// This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. + /// + /// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#PAYMENT_REQUEST. + static const String paymentRequest = 'PAYMENT_REQUEST'; +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index d0ab423344e..77e059af9b6 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -781,6 +781,34 @@ class AndroidWebViewController extends PlatformWebViewController { }; return _webView.settings.setMixedContentMode(androidMode); } + + /// Checks if a WebView feature is supported on the current device. + /// + /// This method uses [android_webview.WebViewFeature.isFeatureSupported] to check + /// if the specified WebView feature is available on the current device and WebView version. + /// + /// See [WebViewFeatureConstants] for available feature constants. + Future isWebViewFeatureSupported(WebViewFeatureType featureType) { + final String feature = switch (featureType) { + WebViewFeatureType.paymentRequest => + WebViewFeatureConstants.paymentRequest, + }; + return _androidWebViewParams.androidWebViewProxy.isWebViewFeatureSupported(feature); + } + + /// Sets whether the WebView should enable the Payment Request API. + /// + /// This method uses [android_webview.WebSettingsCompat.setPaymentRequestEnabled] + /// to enable or disable the Payment Request API for the WebView. + /// + /// Before calling this method, you should check if the feature is supported using + /// [isWebViewFeatureSupported] with [WebViewFeatureConstants.paymentRequest]. + Future setPaymentRequestEnabled(bool enabled) { + return _androidWebViewParams.androidWebViewProxy.setPaymentRequestEnabled( + _webView.settings, + enabled, + ); + } } /// Android implementation of [PlatformWebViewPermissionRequest]. @@ -909,6 +937,12 @@ enum MixedContentMode { neverAllow, } +/// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#constants_1. +enum WebViewFeatureType { + /// Feature for isFeatureSupported. This feature covers setPaymentRequestEnabled, getPaymentRequestEnabled, setHasEnrolledInstrumentEnabled, and getHasEnrolledInstrumentEnabled, + paymentRequest, +} + /// Parameters received when the `WebView` should show a file selector. @immutable class FileSelectorParams { From 8fa5fe10c4d35af5e6fd5f618a9221986bfcf569 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 18:58:11 +0900 Subject: [PATCH 04/20] Add native unit tests for payment request feature --- .../webviewflutter/WebSettingsCompatTest.java | 31 +++++++++++ .../webviewflutter/WebViewFeatureTest.java | 51 ++++++++++++++++++ .../test/android_webview_controller_test.dart | 49 +++++++++++++++++ ...android_webview_controller_test.mocks.dart | 53 +++++++++++++++++++ ...oid_webview_cookie_manager_test.mocks.dart | 22 ++++++++ 5 files changed, 206 insertions(+) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java new file mode 100644 index 00000000000..65481349191 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -0,0 +1,31 @@ +// 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.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import android.webkit.WebSettings; + +import androidx.webkit.WebSettingsCompat; + +import org.junit.Test; +import org.mockito.MockedStatic; + +public class WebSettingsCompatTest { + @Test + public void setPaymentRequestEnabled() { + final PigeonApiWebSettingsCompat api = new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); + + final WebSettings webSettings = mock(WebSettings.class); + try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { + api.setPaymentRequestEnabled(webSettings, true); + mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true)); + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java new file mode 100644 index 00000000000..6d6377b175e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java @@ -0,0 +1,51 @@ +// 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mockStatic; + +import androidx.webkit.WebViewFeature; + +import org.junit.Test; +import org.mockito.MockedStatic; + +public class WebViewFeatureTest { + @Test + public void isFeatureSupported() { + final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); + + try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { + mockedStatic.when(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")).thenReturn(true); + + boolean result = api.isFeatureSupported("PAYMENT_REQUEST"); + + assertTrue(result); + + mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")); + } catch (Exception e) { + fail(e.toString()); + } + } + + @Test + public void isFeatureSupportedReturnsFalse() { + final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); + + try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { + mockedStatic.when(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")).thenReturn(false); + + boolean result = api.isFeatureSupported("UNSUPPORTED_FEATURE"); + + assertFalse(result); + + mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")); + } catch (Exception e) { + fail(e.toString()); + } + } +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index 14a9eba1cfa..36cc3ccc2c1 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -89,6 +89,9 @@ void main() { android_webview.WebViewClient? mockWebViewClient, android_webview.WebStorage? mockWebStorage, android_webview.WebSettings? mockSettings, + Future Function(String)? isWebViewFeatureSupported, + Future Function(android_webview.WebSettings, bool)? + setPaymentRequestEnabled, }) { final android_webview.WebView nonNullMockWebView = mockWebView ?? MockWebView(); @@ -247,6 +250,10 @@ void main() { postMessage, }) => mockJavaScriptChannel ?? MockJavaScriptChannel(), + isWebViewFeatureSupported: + isWebViewFeatureSupported ?? (_) async => false, + setPaymentRequestEnabled: + setPaymentRequestEnabled ?? (_, __) async {}, )); when(nonNullMockWebView.settings) @@ -1710,6 +1717,48 @@ void main() { expect(controller.webViewIdentifier, 0); }); + test('isWebViewFeatureSupported', () async { + String? captured; + const bool expectedIsWebViewFeatureEnabled = true; + + final AndroidWebViewController controller = createControllerWithMocks( + isWebViewFeatureSupported: (String feature) async { + captured = feature; + return expectedIsWebViewFeatureEnabled; + }, + ); + + final bool result = await controller.isWebViewFeatureSupported( + WebViewFeatureType.paymentRequest, + ); + + expect(WebViewFeatureConstants.paymentRequest, captured); + expect(expectedIsWebViewFeatureEnabled, result); + }); + + test('setPaymentRequestEnabled', () async { + android_webview.WebSettings? capturedSettings; + bool? capturedEnabled; + const bool expectedEnabled = true; + + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + setPaymentRequestEnabled: + (android_webview.WebSettings settings, bool enabled) async { + capturedSettings = settings; + capturedEnabled = enabled; + }, + ); + + await controller.setPaymentRequestEnabled(expectedEnabled); + + expect(mockSettings, capturedSettings); + expect(expectedEnabled, capturedEnabled); + }); + group('AndroidWebViewWidget', () { testWidgets('Builds Android view using supplied parameters', (WidgetTester tester) async { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index a786a9394c9..44b94ecd49f 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -1018,6 +1018,29 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); + + @override + _i8.Future isWebViewFeatureSupported( + _i7.WebViewFeatureType? featureType) => + (super.noSuchMethod( + Invocation.method( + #isWebViewFeatureSupported, + [featureType], + ), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); + + @override + _i8.Future setPaymentRequestEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setPaymentRequestEnabled, + [enabled], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [AndroidWebViewProxy]. @@ -1829,6 +1852,36 @@ class MockAndroidWebViewProxy extends _i1.Mock Invocation.getter(#instanceWebStorage), ), ) as _i2.WebStorage Function()); + + @override + _i8.Future Function(String) get isWebViewFeatureSupported => + (super.noSuchMethod( + Invocation.getter(#isWebViewFeatureSupported), + returnValue: (String __p0) => _i8.Future.value(false), + returnValueForMissingStub: (String __p0) => + _i8.Future.value(false), + ) as _i8.Future Function(String)); + + @override + _i8.Future Function( + _i2.WebSettings, + bool, + ) get setPaymentRequestEnabled => (super.noSuchMethod( + Invocation.getter(#setPaymentRequestEnabled), + returnValue: ( + _i2.WebSettings __p0, + bool __p1, + ) => + _i8.Future.value(), + returnValueForMissingStub: ( + _i2.WebSettings __p0, + bool __p1, + ) => + _i8.Future.value(), + ) as _i8.Future Function( + _i2.WebSettings, + bool, + )); } /// A class which mocks [AndroidWebViewWidgetCreationParams]. diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart index b9e807b88ad..c305241cbf6 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart @@ -709,4 +709,26 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + + @override + _i5.Future isWebViewFeatureSupported( + _i6.WebViewFeatureType? featureType) => + (super.noSuchMethod( + Invocation.method( + #isWebViewFeatureSupported, + [featureType], + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + + @override + _i5.Future setPaymentRequestEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method( + #setPaymentRequestEnabled, + [enabled], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } From 381ae6f4f4d6182b114da166f6dcaf0158cd7757 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 18:58:38 +0900 Subject: [PATCH 05/20] Add sample menu for payment request --- .../example/lib/main.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 6608c7bebbd..b83c41f6b9d 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -350,6 +350,7 @@ enum MenuOptions { basicAuthentication, javaScriptAlert, viewportMeta, + checkWebViewPaymentRequestFeatureEnabled, } class SampleMenu extends StatelessWidget { @@ -407,6 +408,8 @@ class SampleMenu extends StatelessWidget { _onJavaScriptAlertExample(context); case MenuOptions.viewportMeta: _onViewportMetaExample(); + case MenuOptions.checkWebViewPaymentRequestFeatureEnabled: + _onWebViewFeatureExample(context); } }, itemBuilder: (BuildContext context) => >[ @@ -483,6 +486,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.viewportMeta, child: Text('Viewport meta example'), ), + const PopupMenuItem( + value: MenuOptions.checkWebViewPaymentRequestFeatureEnabled, + child: Text('WebView Feature Example'), + ), ], ); } @@ -783,6 +790,24 @@ class SampleMenu extends StatelessWidget { Future _onViewportMetaExample() { return webViewController.loadHtmlString(kViewportMetaPage); } + + Future _onWebViewFeatureExample(BuildContext context) async { + final AndroidWebViewController androidController = + webViewController as AndroidWebViewController; + final bool paymentRequestEnabled = await androidController + .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); + + // Call this method to enable the Payment Request API. + // if (paymentRequestEnabled) { + // await androidController.setPaymentRequestEnabled(true); + // } + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Payment Request API enabled: $paymentRequestEnabled'), + )); + } + } } class NavigationControls extends StatelessWidget { From 5eee7b3f75de5285280ad18772529b3ea7448054 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 21:23:49 +0900 Subject: [PATCH 06/20] Prepare CHANGELOG --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 4 ++++ packages/webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index b26a46de5e7..f56c3be547e 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.9.0 + +* Adds support to check payment request is supported. + ## 4.8.2 * Bumps gradle from 8.9.0 to 8.11.1. diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 2c4bd97e8f0..33eafa68d12 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/tree/main/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: 4.8.2 +version: 4.9.0 environment: sdk: ^3.6.0 From d43bde1bb6bead45a12cbe38a5dd6e47f9c8bb57 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 21:49:49 +0900 Subject: [PATCH 07/20] Run auto-formatter --- .../WebSettingsCompatProxyApi.java | 2 +- .../WebViewFeatureProxyApi.java | 2 +- .../webviewflutter/WebSettingsCompatTest.java | 5 ++--- .../webviewflutter/WebViewFeatureTest.java | 21 +++++++++++-------- .../lib/src/android_webview_controller.dart | 3 ++- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java index 19e42ba5afd..c10bfa80e23 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -22,4 +22,4 @@ public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) { WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); } -} \ No newline at end of file +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java index 954bdb400cb..717a12ab366 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java @@ -21,4 +21,4 @@ public WebViewFeatureProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { public boolean isFeatureSupported(@NonNull String feature) { return WebViewFeature.isFeatureSupported(feature); } -} \ No newline at end of file +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java index 65481349191..9ad8cca7d93 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -9,16 +9,15 @@ import static org.mockito.Mockito.mockStatic; import android.webkit.WebSettings; - import androidx.webkit.WebSettingsCompat; - import org.junit.Test; import org.mockito.MockedStatic; public class WebSettingsCompatTest { @Test public void setPaymentRequestEnabled() { - final PigeonApiWebSettingsCompat api = new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); + final PigeonApiWebSettingsCompat api = + new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); final WebSettings webSettings = mock(WebSettings.class); try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java index 6d6377b175e..a30b13a9b7c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.mockStatic; import androidx.webkit.WebViewFeature; - import org.junit.Test; import org.mockito.MockedStatic; @@ -20,10 +19,12 @@ public void isFeatureSupported() { final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { - mockedStatic.when(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")).thenReturn(true); - + mockedStatic + .when(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")) + .thenReturn(true); + boolean result = api.isFeatureSupported("PAYMENT_REQUEST"); - + assertTrue(result); mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")); @@ -37,15 +38,17 @@ public void isFeatureSupportedReturnsFalse() { final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { - mockedStatic.when(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")).thenReturn(false); - + mockedStatic + .when(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")) + .thenReturn(false); + boolean result = api.isFeatureSupported("UNSUPPORTED_FEATURE"); - + assertFalse(result); - + mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")); } catch (Exception e) { fail(e.toString()); } } -} \ No newline at end of file +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 77e059af9b6..b5df33d9d3a 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -793,7 +793,8 @@ class AndroidWebViewController extends PlatformWebViewController { WebViewFeatureType.paymentRequest => WebViewFeatureConstants.paymentRequest, }; - return _androidWebViewParams.androidWebViewProxy.isWebViewFeatureSupported(feature); + return _androidWebViewParams.androidWebViewProxy + .isWebViewFeatureSupported(feature); } /// Sets whether the WebView should enable the Payment Request API. From a20f0fbf1a0efa16e40ecdca421d5c019a800feb Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 22:00:51 +0900 Subject: [PATCH 08/20] Simplify method description --- .../webview_flutter_android/example/lib/main.dart | 2 +- .../lib/src/android_webview_controller.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b83c41f6b9d..2fdfec79e23 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -804,7 +804,7 @@ class SampleMenu extends StatelessWidget { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Payment Request API enabled: $paymentRequestEnabled'), + content: Text('Payment Request API supported: $paymentRequestEnabled'), )); } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index b5df33d9d3a..7476c664e26 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -940,7 +940,7 @@ enum MixedContentMode { /// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#constants_1. enum WebViewFeatureType { - /// Feature for isFeatureSupported. This feature covers setPaymentRequestEnabled, getPaymentRequestEnabled, setHasEnrolledInstrumentEnabled, and getHasEnrolledInstrumentEnabled, + /// Feature for isFeatureSupported. This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. paymentRequest, } From 103e29f420507f64aa7b0287cd243cdb2c2dad90 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 22:01:06 +0900 Subject: [PATCH 09/20] Specify details with added methods --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index f56c3be547e..e132d9a81a5 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,6 +1,6 @@ ## 4.9.0 -* Adds support to check payment request is supported. +* Adds support for the Payment Request API with `AndroidWebViewController.isWebViewFeatureSupported` and `AndroidWebViewController.setPaymentRequestEnabled`. ## 4.8.2 From 8c735175567d3a52be9724947e108f868f974df6 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 22:58:23 +0900 Subject: [PATCH 10/20] Fix RequiresFeature lint: setPaymentRequestEnabled should only be called if the feature PAYMENT_REQUEST is present Ensures the function works correctly even when called without prior WebViewFeature.isFeatureSupported check. ref. https://developers.google.com/pay/api/android/guides/recipes/using-android-webview#enable-payment-request-api --- .../WebSettingsCompatProxyApi.java | 5 ++- .../webviewflutter/WebSettingsCompatTest.java | 35 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java index c10bfa80e23..c27917f3d16 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -7,6 +7,7 @@ import android.webkit.WebSettings; import androidx.annotation.NonNull; import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewFeature; /** * Host api implementation for {@link WebSettingsCompat}. @@ -20,6 +21,8 @@ public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { @Override public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) { - WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); + if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) { + WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); + } } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java index 9ad8cca7d93..14a3441348e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -7,22 +7,51 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; import android.webkit.WebSettings; import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewFeature; + import org.junit.Test; import org.mockito.MockedStatic; public class WebSettingsCompatTest { @Test - public void setPaymentRequestEnabled() { + public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_supported() { final PigeonApiWebSettingsCompat api = new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); final WebSettings webSettings = mock(WebSettings.class); + + try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { + try(MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { + mockedWebViewFeature.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)).thenReturn(true); + api.setPaymentRequestEnabled(webSettings, true); + mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true)); + } catch (Exception e) { + fail(e.toString()); + } + } catch (Exception e) { + fail(e.toString()); + } + } + + @Test + public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_NOT_supported() { + final PigeonApiWebSettingsCompat api = + new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); + + final WebSettings webSettings = mock(WebSettings.class); + try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { - api.setPaymentRequestEnabled(webSettings, true); - mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true)); + try(MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { + mockedWebViewFeature.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)).thenReturn(false); + api.setPaymentRequestEnabled(webSettings, true); + mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true), never()); + } catch (Exception e) { + fail(e.toString()); + } } catch (Exception e) { fail(e.toString()); } From 5ed43ab786a298b08fea0deef70bd461d9dc55e8 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 26 Jul 2025 23:32:41 +0900 Subject: [PATCH 11/20] Fix format --- .../webviewflutter/WebSettingsCompatTest.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java index 14a3441348e..17aa43313e1 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -12,7 +12,6 @@ import android.webkit.WebSettings; import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; - import org.junit.Test; import org.mockito.MockedStatic; @@ -25,8 +24,10 @@ public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_supported() { final WebSettings webSettings = mock(WebSettings.class); try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { - try(MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { - mockedWebViewFeature.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)).thenReturn(true); + try (MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { + mockedWebViewFeature + .when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) + .thenReturn(true); api.setPaymentRequestEnabled(webSettings, true); mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true)); } catch (Exception e) { @@ -40,15 +41,18 @@ public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_supported() { @Test public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_NOT_supported() { final PigeonApiWebSettingsCompat api = - new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); + new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); final WebSettings webSettings = mock(WebSettings.class); try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { - try(MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { - mockedWebViewFeature.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)).thenReturn(false); + try (MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { + mockedWebViewFeature + .when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) + .thenReturn(false); api.setPaymentRequestEnabled(webSettings, true); - mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true), never()); + mockedStatic.verify( + () -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true), never()); } catch (Exception e) { fail(e.toString()); } From 28f88893f20d6cd0e440be2421c8959aaad67747 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 1 Aug 2025 14:56:58 +0900 Subject: [PATCH 12/20] Fix doc comments to correspond to the method --- .../webviewflutter/WebSettingsCompatProxyApi.java | 5 +++-- .../plugins/webviewflutter/WebViewFeatureProxyApi.java | 5 +++-- .../lib/src/android_webview_controller.dart | 9 ++++++++- .../webview_flutter_android/pigeons/android_webkit.dart | 6 ++++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java index c27917f3d16..cc2e205adfe 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -10,9 +10,10 @@ import androidx.webkit.WebViewFeature; /** - * Host api implementation for {@link WebSettingsCompat}. + * Proxy API implementation for {@link WebSettingsCompat}. * - *

Handles static methods for {@link WebSettingsCompat}. + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. */ public class WebSettingsCompatProxyApi extends PigeonApiWebSettingsCompat { public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java index 717a12ab366..e1a58ad35dd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java @@ -8,9 +8,10 @@ import androidx.webkit.WebViewFeature; /** - * Host api implementation for {@link WebViewFeature}. + * Proxy API implementation for {@link WebViewFeature}. * - *

Handles creating {@link WebViewFeature}s that intercommunicate with a paired Dart object. + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. */ public class WebViewFeatureProxyApi extends PigeonApiWebViewFeature { public WebViewFeatureProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 7476c664e26..22be7fe1aee 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -804,6 +804,9 @@ class AndroidWebViewController extends PlatformWebViewController { /// /// Before calling this method, you should check if the feature is supported using /// [isWebViewFeatureSupported] with [WebViewFeatureConstants.paymentRequest]. + /// + /// This feature requires adding queries to the AndroidManifest.xml to allow WebView to query the device for the user's payment applications: + /// See https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setPaymentRequestEnabled(android.webkit.WebSettings,boolean). Future setPaymentRequestEnabled(bool enabled) { return _androidWebViewParams.androidWebViewProxy.setPaymentRequestEnabled( _webView.settings, @@ -938,9 +941,13 @@ enum MixedContentMode { neverAllow, } +/// WebView support library feature types used to query for support on the device. +/// /// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#constants_1. enum WebViewFeatureType { - /// Feature for isFeatureSupported. This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. + /// Feature for isFeatureSupported. + /// + /// This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. paymentRequest, } diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart index 4eaf1740930..9dadba738d8 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -1092,6 +1092,9 @@ abstract class Certificate { Uint8List getEncoded(); } +/// Compatibility version of `WebSettings`. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. @ProxyApi( kotlinOptions: KotlinProxyApiOptions( fullClassName: 'androidx.webkit.WebSettingsCompat', @@ -1105,6 +1108,9 @@ abstract class WebSettingsCompat { ); } +/// Utility class for checking which WebView Support Library features are supported on the device. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. @ProxyApi( kotlinOptions: KotlinProxyApiOptions( fullClassName: 'androidx.webkit.WebViewFeature', From 3f68c0e88284a78de1e8beefb28ab4d54a47849c Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 1 Aug 2025 15:00:26 +0900 Subject: [PATCH 13/20] Remove sample for WebSettingsCompat and WebViewFeature. method doc is enough --- .../example/lib/main.dart | 25 ------------------- 1 file changed, 25 deletions(-) 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 2fdfec79e23..6608c7bebbd 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -350,7 +350,6 @@ enum MenuOptions { basicAuthentication, javaScriptAlert, viewportMeta, - checkWebViewPaymentRequestFeatureEnabled, } class SampleMenu extends StatelessWidget { @@ -408,8 +407,6 @@ class SampleMenu extends StatelessWidget { _onJavaScriptAlertExample(context); case MenuOptions.viewportMeta: _onViewportMetaExample(); - case MenuOptions.checkWebViewPaymentRequestFeatureEnabled: - _onWebViewFeatureExample(context); } }, itemBuilder: (BuildContext context) => >[ @@ -486,10 +483,6 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.viewportMeta, child: Text('Viewport meta example'), ), - const PopupMenuItem( - value: MenuOptions.checkWebViewPaymentRequestFeatureEnabled, - child: Text('WebView Feature Example'), - ), ], ); } @@ -790,24 +783,6 @@ class SampleMenu extends StatelessWidget { Future _onViewportMetaExample() { return webViewController.loadHtmlString(kViewportMetaPage); } - - Future _onWebViewFeatureExample(BuildContext context) async { - final AndroidWebViewController androidController = - webViewController as AndroidWebViewController; - final bool paymentRequestEnabled = await androidController - .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); - - // Call this method to enable the Payment Request API. - // if (paymentRequestEnabled) { - // await androidController.setPaymentRequestEnabled(true); - // } - - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Payment Request API supported: $paymentRequestEnabled'), - )); - } - } } class NavigationControls extends StatelessWidget { From 878fd383ea3305f759a058c63399126a3b0b7c48 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 1 Aug 2025 15:09:34 +0900 Subject: [PATCH 14/20] Update generated files according to comment update --- .../plugins/webviewflutter/AndroidWebkitLibrary.g.kt | 12 ++++++++++-- .../lib/src/android_webkit.g.dart | 6 ++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt index c0dad62a75d..3a9932d3f4f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt @@ -6331,7 +6331,11 @@ abstract class PigeonApiCertificate( } } } - +/** + * Compatibility version of `WebSettings`. + * + * See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. + */ @Suppress("UNCHECKED_CAST") abstract class PigeonApiWebSettingsCompat( open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar @@ -6406,7 +6410,11 @@ abstract class PigeonApiWebSettingsCompat( } } } - +/** + * Utility class for checking which WebView Support Library features are supported on the device. + * + * See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. + */ @Suppress("UNCHECKED_CAST") abstract class PigeonApiWebViewFeature( open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart index 3dbef1ed227..12f7d30aa5f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart @@ -8354,6 +8354,9 @@ class Certificate extends PigeonInternalProxyApiBaseClass { } } +/// Compatibility version of `WebSettings`. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. class WebSettingsCompat extends PigeonInternalProxyApiBaseClass { /// Constructs [WebSettingsCompat] without creating the associated native object. /// @@ -8462,6 +8465,9 @@ class WebSettingsCompat extends PigeonInternalProxyApiBaseClass { } } +/// Utility class for checking which WebView Support Library features are supported on the device. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. class WebViewFeature extends PigeonInternalProxyApiBaseClass { /// Constructs [WebViewFeature] without creating the associated native object. /// From b0388bcac1d89a6925437ad30466f472ae0cddf4 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Sat, 2 Aug 2025 14:37:59 +0900 Subject: [PATCH 15/20] Specify collect type --- .../lib/src/android_webview_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 22be7fe1aee..21c3c0f8390 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -787,7 +787,7 @@ class AndroidWebViewController extends PlatformWebViewController { /// This method uses [android_webview.WebViewFeature.isFeatureSupported] to check /// if the specified WebView feature is available on the current device and WebView version. /// - /// See [WebViewFeatureConstants] for available feature constants. + /// See [WebViewFeatureType] for available feature constants. Future isWebViewFeatureSupported(WebViewFeatureType featureType) { final String feature = switch (featureType) { WebViewFeatureType.paymentRequest => @@ -803,7 +803,7 @@ class AndroidWebViewController extends PlatformWebViewController { /// to enable or disable the Payment Request API for the WebView. /// /// Before calling this method, you should check if the feature is supported using - /// [isWebViewFeatureSupported] with [WebViewFeatureConstants.paymentRequest]. + /// [isWebViewFeatureSupported] with [WebViewFeatureType.paymentRequest]. /// /// This feature requires adding queries to the AndroidManifest.xml to allow WebView to query the device for the user's payment applications: /// See https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setPaymentRequestEnabled(android.webkit.WebSettings,boolean). From ddea6d4da9d677e587160ff9213b33e4693f11a9 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 8 Aug 2025 14:35:22 +0900 Subject: [PATCH 16/20] Client should use setPaymentRequestEnabled if only WebViewFeatureProxyApi#isFeatureSupported returns true --- .../webviewflutter/WebSettingsCompatProxyApi.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java index cc2e205adfe..c949d106f36 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -4,10 +4,10 @@ package io.flutter.plugins.webviewflutter; +import android.annotation.SuppressLint; import android.webkit.WebSettings; import androidx.annotation.NonNull; import androidx.webkit.WebSettingsCompat; -import androidx.webkit.WebViewFeature; /** * Proxy API implementation for {@link WebSettingsCompat}. @@ -20,10 +20,13 @@ public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { super(pigeonRegistrar); } + /** + * This method should only be called if {@link WebViewFeatureProxyApi#isFeatureSupported(String)} + * with PAYMENT_REQUEST returns true. + */ + @SuppressLint("RequiresFeature") @Override public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) { - if (WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) { - WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); - } + WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); } } From 2b06e3dea8e68e7411de40dba4957d2c920140cd Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 8 Aug 2025 14:37:08 +0900 Subject: [PATCH 17/20] Add Payment Request section for webview_flutter_android --- .../webview_flutter_android/README.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md index 214b50c079b..451b907185b 100644 --- a/packages/webview_flutter/webview_flutter_android/README.md +++ b/packages/webview_flutter/webview_flutter_android/README.md @@ -52,6 +52,35 @@ Java: import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi; ``` +## Enable Payment Request in WebView + +Payment Request feature can be enabled by `AndroidWebViewController.setPaymentRequestEnabled` after checking `AndroidWebViewController.isWebViewFeatureSupported`. + +```dart +final bool paymentRequestEnabled = await androidController + .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); + +if (paymentRequestEnabled) { + await androidController.setPaymentRequestEnabled(true); +} +``` + +Add intent filters to your AndroidManifest.xml to discover and invoke Android payment apps using system intents: + +```xml + + + + + + + + + + + +``` + ## Fullscreen Video To display a video as fullscreen, an app must manually handle the notification that the current page From 733719994bb4572fe852c2bc2d961f3f9b4c4afd Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 8 Aug 2025 15:32:37 +0900 Subject: [PATCH 18/20] Fix tests according to SuppressLint --- .../webviewflutter/WebSettingsCompatTest.java | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java index 17aa43313e1..2219fa555f6 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -7,7 +7,6 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; import android.webkit.WebSettings; import androidx.webkit.WebSettingsCompat; @@ -17,7 +16,7 @@ public class WebSettingsCompatTest { @Test - public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_supported() { + public void setPaymentRequestEnabled() { final PigeonApiWebSettingsCompat api = new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); @@ -37,27 +36,4 @@ public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_supported() { fail(e.toString()); } } - - @Test - public void setPaymentRequestEnabled_PAYMENT_REQUEST_is_NOT_supported() { - final PigeonApiWebSettingsCompat api = - new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); - - final WebSettings webSettings = mock(WebSettings.class); - - try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { - try (MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { - mockedWebViewFeature - .when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) - .thenReturn(false); - api.setPaymentRequestEnabled(webSettings, true); - mockedStatic.verify( - () -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true), never()); - } catch (Exception e) { - fail(e.toString()); - } - } catch (Exception e) { - fail(e.toString()); - } - } } From 47b6b6fadd2f0a083a91818c94a87a87c7d72b03 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 8 Aug 2025 16:23:30 +0900 Subject: [PATCH 19/20] Address code-excerpt --- .../webview_flutter_android/README.md | 1 + .../example/lib/main.dart | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md index 451b907185b..0a2ff2125b5 100644 --- a/packages/webview_flutter/webview_flutter_android/README.md +++ b/packages/webview_flutter/webview_flutter_android/README.md @@ -56,6 +56,7 @@ import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi; Payment Request feature can be enabled by `AndroidWebViewController.setPaymentRequestEnabled` after checking `AndroidWebViewController.isWebViewFeatureSupported`. + ```dart final bool paymentRequestEnabled = await androidController .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); 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 6608c7bebbd..09a39203d2d 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -350,6 +350,7 @@ enum MenuOptions { basicAuthentication, javaScriptAlert, viewportMeta, + checkWebViewPaymentRequestFeatureEnabled, } class SampleMenu extends StatelessWidget { @@ -407,6 +408,8 @@ class SampleMenu extends StatelessWidget { _onJavaScriptAlertExample(context); case MenuOptions.viewportMeta: _onViewportMetaExample(); + case MenuOptions.checkWebViewPaymentRequestFeatureEnabled: + _onWebViewFeatureExample(context); } }, itemBuilder: (BuildContext context) => >[ @@ -483,6 +486,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.viewportMeta, child: Text('Viewport meta example'), ), + const PopupMenuItem( + value: MenuOptions.checkWebViewPaymentRequestFeatureEnabled, + child: Text('WebView Feature Example'), + ), ], ); } @@ -783,6 +790,25 @@ class SampleMenu extends StatelessWidget { Future _onViewportMetaExample() { return webViewController.loadHtmlString(kViewportMetaPage); } + + Future _onWebViewFeatureExample(BuildContext context) async { + final AndroidWebViewController androidController = + webViewController as AndroidWebViewController; + // #docregion payment_request_example + final bool paymentRequestEnabled = await androidController + .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); + + if (paymentRequestEnabled) { + await androidController.setPaymentRequestEnabled(true); + } + + // #enddocregion payment_request_example + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Payment Request API supported: $paymentRequestEnabled'), + )); + } + } } class NavigationControls extends StatelessWidget { From 1cb2a2066d39bb092c1c59fdd094fcf19a106fd0 Mon Sep 17 00:00:00 2001 From: Takuma Homma Date: Fri, 8 Aug 2025 16:32:59 +0900 Subject: [PATCH 20/20] Run update-excerpts --- .../webview_flutter_android/example/lib/main.dart | 1 - 1 file changed, 1 deletion(-) 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 09a39203d2d..6f877b989f4 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -801,7 +801,6 @@ class SampleMenu extends StatelessWidget { if (paymentRequestEnabled) { await androidController.setPaymentRequestEnabled(true); } - // #enddocregion payment_request_example if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(