From caa1f51d44322469bbe029b7e70f394a9dff5688 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 11:27:46 -0400 Subject: [PATCH 01/12] Scrub camera --- packages/camera/camera/README.md | 8 +------ packages/camera/camera_android/CHANGELOG.md | 5 ++-- .../camera_android/android/build.gradle | 2 +- .../io/flutter/plugins/camera/Camera.java | 14 ++--------- .../flutter/plugins/camera/CameraApiImpl.java | 3 --- .../plugins/camera/CameraProperties.java | 1 - .../plugins/camera/CameraPropertiesImpl.java | 1 - .../plugins/camera/SdkCapabilityChecker.java | 12 ---------- .../noisereduction/NoiseReductionFeature.java | 11 ++++----- .../io/flutter/plugins/camera/CameraTest.java | 24 ++----------------- .../NoiseReductionFeatureTest.java | 16 ------------- .../example/lib/camera_controller.dart | 4 ---- .../camera_android/example/pubspec.yaml | 4 ++-- packages/camera/camera_android/pubspec.yaml | 6 ++--- 14 files changed, 18 insertions(+), 93 deletions(-) diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index 9f5e6e296e2..cb016d68c96 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -8,7 +8,7 @@ A Flutter plugin for iOS, Android and Web allowing access to the device cameras. | | Android | iOS | Web | |----------------|---------|-----------|------------------------| -| **Support** | SDK 21+ | iOS 12.0+ | [See `camera_web `][1] | +| **Support** | SDK 24+ | iOS 12.0+ | [See `camera_web `][1] | ## Features @@ -37,12 +37,6 @@ If editing `Info.plist` as text, add: ### Android -Change the minimum Android sdk version to 21 (or higher) in your `android/app/build.gradle` file. - -```groovy -minSdkVersion 21 -``` - The endorsed [`camera_android_camerax`][2] implementation of the camera plugin built with CameraX has better support for more devices than `camera_android`, but has some limitations; please see [this list][3] for more details. If you wish to use the [`camera_android`][4] implementation of the camera plugin diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index c150cd658ea..78141dd7993 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.10.10+6 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates minimum supported SDK version to Flutter 3.35. +* Removes code for supporting API 21-23. ## 0.10.10+5 diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index bc246342146..4432a03db1d 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -34,7 +34,7 @@ buildFeatures { compileSdk = 36 defaultConfig { - minSdkVersion 21 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index 9d398b2e025..94c8b9ed899 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -891,12 +891,7 @@ public void pauseVideoRecording() { } try { - if (SdkCapabilityChecker.supportsVideoPause()) { - mediaRecorder.pause(); - } else { - throw new Messages.FlutterError( - "videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - } + mediaRecorder.pause(); } catch (IllegalStateException e) { throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } @@ -908,12 +903,7 @@ public void resumeVideoRecording() { } try { - if (SdkCapabilityChecker.supportsVideoPause()) { - mediaRecorder.resume(); - } else { - throw new Messages.FlutterError( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - } + mediaRecorder.resume(); } catch (IllegalStateException e) { throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java index 324403e929e..b9e34a9a090 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java @@ -86,9 +86,6 @@ private Long instantiateCamera(String cameraName, Messages.PlatformMediaSettings return flutterSurfaceTexture.id(); } - // We move catching CameraAccessException out of onMethodCall because it causes a crash - // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to - // to be able to compile with <21 sdks for apps that want the camera and support earlier version. @SuppressWarnings("ConstantConditions") private void handleException(Exception exception, Messages.Result result) { // The code below exactly preserves the format of the native exceptions generated by pre-Pigeon diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java index 2c3eda880de..8bf5de0046f 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -224,7 +224,6 @@ public interface CameraProperties { * @return android.graphics.Rect Area of the image sensor which corresponds to active pixels prior * to the application of any geometric distortion correction. */ - @RequiresApi(api = VERSION_CODES.M) @NonNull Rect getSensorInfoPreCorrectionActiveArraySize(); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPropertiesImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPropertiesImpl.java index dc8e9b736f2..bedb69ef0a2 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPropertiesImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPropertiesImpl.java @@ -140,7 +140,6 @@ public Size getSensorInfoPixelArraySize() { return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); } - @RequiresApi(api = VERSION_CODES.M) @NonNull @Override public Rect getSensorInfoPreCorrectionActiveArraySize() { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java index 1ff512672d9..0770f257838 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java @@ -34,24 +34,12 @@ public static boolean supportsEncoderProfiles() { return SDK_VERSION >= Build.VERSION_CODES.S; } - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.M) - public static boolean supportsMarshmallowNoiseReductionModes() { - // See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES - return SDK_VERSION >= Build.VERSION_CODES.M; - } - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) public static boolean supportsSessionConfiguration() { // See https://developer.android.com/reference/android/hardware/camera2/params/SessionConfiguration return SDK_VERSION >= Build.VERSION_CODES.P; } - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N) - public static boolean supportsVideoPause() { - // See https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent.Pause - return SDK_VERSION >= Build.VERSION_CODES.N; - } - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) public static boolean supportsZoomRatio() { // See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_ZOOM_RATIO diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java index ae7c2262ea7..6bff002faba 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java @@ -10,7 +10,6 @@ import androidx.annotation.NonNull; import io.flutter.BuildConfig; import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.SdkCapabilityChecker; import io.flutter.plugins.camera.features.CameraFeature; import java.util.HashMap; @@ -35,12 +34,10 @@ public NoiseReductionFeature(@NonNull CameraProperties cameraProperties) { NOISE_REDUCTION_MODES.put(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST); NOISE_REDUCTION_MODES.put( NoiseReductionMode.highQuality, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY); - if (SdkCapabilityChecker.supportsMarshmallowNoiseReductionModes()) { - NOISE_REDUCTION_MODES.put( - NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL); - NOISE_REDUCTION_MODES.put( - NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG); - } + NOISE_REDUCTION_MODES.put( + NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL); + NOISE_REDUCTION_MODES.put( + NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG); } @NonNull diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index da3ab44312b..d4242c60776 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -669,32 +669,22 @@ public void pauseVideoRecording_shouldNotThrowWhenNotRecording() { } @Test - public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { + public void pauseVideoRecording_shouldCallPauseWhenRecording() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); camera.mediaRecorder = mockMediaRecorder; camera.recordingVideo = true; - SdkCapabilityChecker.SDK_VERSION = 24; camera.pauseVideoRecording(); verify(mockMediaRecorder, times(1)).pause(); } - @Test - public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() { - camera.recordingVideo = true; - SdkCapabilityChecker.SDK_VERSION = 23; - - assertThrows(Messages.FlutterError.class, camera::pauseVideoRecording); - } - @Test public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); camera.mediaRecorder = mockMediaRecorder; camera.recordingVideo = true; - SdkCapabilityChecker.SDK_VERSION = 24; IllegalStateException expectedException = new IllegalStateException("Test error message"); @@ -711,11 +701,10 @@ public void resumeVideoRecording_shouldNotThrowWhenNotRecording() { } @Test - public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { + public void resumeVideoRecording_shouldCallPauseWhenRecording() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); camera.mediaRecorder = mockMediaRecorder; camera.recordingVideo = true; - SdkCapabilityChecker.SDK_VERSION = 24; camera.resumeVideoRecording(); @@ -866,15 +855,6 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { () -> camera.setDescriptionWhileRecording(newCameraProperties)); } - @Test - public void - resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() { - camera.recordingVideo = true; - SdkCapabilityChecker.SDK_VERSION = 23; - - assertThrows(Messages.FlutterError.class, camera::resumeVideoRecording); - } - @Test public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() { diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java index 93f81c6c153..9aae6b540d5 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java @@ -16,25 +16,9 @@ import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.SdkCapabilityChecker; -import org.junit.After; -import org.junit.Before; import org.junit.Test; public class NoiseReductionFeatureTest { - @Before - public void before() { - // Make sure the SDK_VERSION field returns 23, to allow using all available - // noise reduction modes in tests. - SdkCapabilityChecker.SDK_VERSION = 23; - } - - @After - public void after() { - // Make sure we reset the SDK_VERSION field to it's original value. - SdkCapabilityChecker.SDK_VERSION = 0; - } - @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index 6eb6b753e0e..156644f1f88 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -364,16 +364,12 @@ class CameraController extends ValueNotifier { } /// Pause video recording. - /// - /// This feature is only available on iOS and Android sdk 24+. Future pauseVideoRecording() async { await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); } /// Resume video recording after pausing. - /// - /// This feature is only available on iOS and Android sdk 24+. Future resumeVideoRecording() async { await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index 8a362e8c5e1..f4d614defd6 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera plugin. publish_to: none environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" dependencies: camera_android: diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index a9c4a7d27af..db3386bdd37 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,11 +3,11 @@ description: Android implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.10+5 +version: 0.10.10+6 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" flutter: plugin: From 58d0780cc6688f83edfc0bcb06f5149ba44b58bf Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 11:33:48 -0400 Subject: [PATCH 02/12] Scrub local_auth --- packages/local_auth/local_auth/README.md | 11 ++++------- packages/local_auth/local_auth_android/CHANGELOG.md | 5 +++-- .../local_auth_android/android/build.gradle | 2 +- .../io/flutter/plugins/localauth/LocalAuthPlugin.java | 2 +- .../local_auth_android/example/pubspec.yaml | 4 ++-- packages/local_auth/local_auth_android/pubspec.yaml | 6 +++--- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 2a7e4c043a8..e09626472e7 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -8,9 +8,9 @@ the user. On supported devices, this includes authentication with biometrics such as fingerprint or facial recognition. -| | Android | iOS | macOS | Windows | -|-------------|-----------|-------|--------|-------------| -| **Support** | SDK 21+\* | 12.0+ | 10.14+ | Windows 10+ | +| | Android | iOS | macOS | Windows | +|-------------|---------|-------|--------|-------------| +| **Support** | SDK 24+ | 12.0+ | 10.14+ | Windows 10+ | ## Usage @@ -203,12 +203,9 @@ app has not been updated to use Face ID. ## Android Integration -\* The plugin will build and run on SDK 21+, but `isDeviceSupported()` will -always return false before SDK 23 (Android 6.0). - ### Activity Changes -Note that `local_auth` requires the use of a `FragmentActivity` instead of an +`local_auth` requires the use of a `FragmentActivity` instead of an `Activity`. To update your application: * If you are using `FlutterActivity` directly, change it to diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 29be4e8936e..bdaaed1c4c4 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 1.0.52 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Removes obsolete code related to supporting SDK <24. +* Updates minimum supported SDK version to Flutter 3.35. ## 1.0.51 diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index 228d1a8474d..4259d201b3f 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -26,7 +26,7 @@ android { compileSdk = flutter.compileSdkVersion defaultConfig { - minSdkVersion 21 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java index 09af7c6656c..cb0124bb291 100644 --- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java @@ -172,7 +172,7 @@ void onAuthenticationCompleted(Result result, AuthResult value) { @VisibleForTesting public boolean isDeviceSecure() { if (keyguardManager == null) return false; - return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); + return keyguardManager.isDeviceSecure(); } private boolean canAuthenticateWithBiometrics() { diff --git a/packages/local_auth/local_auth_android/example/pubspec.yaml b/packages/local_auth/local_auth_android/example/pubspec.yaml index 3a10bee4329..c75acd437c4 100644 --- a/packages/local_auth/local_auth_android/example/pubspec.yaml +++ b/packages/local_auth/local_auth_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the local_auth_android plugin. publish_to: none environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index 2a1565e61c0..89503983d63 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -2,11 +2,11 @@ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.51 +version: 1.0.52 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" flutter: plugin: From e54e316b6c9effe8ea5c7b51ae5930abf9dc7f07 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 11:39:35 -0400 Subject: [PATCH 03/12] Scrub image_picker_android --- packages/image_picker/image_picker/README.md | 5 +---- packages/image_picker/image_picker_android/CHANGELOG.md | 5 +++-- .../image_picker/image_picker_android/android/build.gradle | 2 +- .../flutter/plugins/imagepicker/ImagePickerDelegate.java | 4 ---- .../io/flutter/plugins/imagepicker/ImagePickerUtils.java | 7 +++---- .../image_picker/image_picker_android/example/pubspec.yaml | 4 ++-- packages/image_picker/image_picker_android/pubspec.yaml | 6 +++--- 7 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index 59bd065a2ae..39f1d9b8e73 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -8,7 +8,7 @@ and taking new pictures with the camera. | | Android | iOS | Linux | macOS | Web | Windows | |-------------|---------|---------|-------|--------|---------------------------------|-------------| -| **Support** | SDK 21+ | iOS 12+ | Any | 10.14+ | [See `image_picker_for_web`](https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform) | Windows 10+ | +| **Support** | SDK 24+ | iOS 12+ | Any | 10.14+ | [See `image_picker_for_web`](https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform) | Windows 10+ | ## Setup @@ -38,9 +38,6 @@ _Privacy - Microphone Usage Description_ in the visual editor. ### Android -Starting with version **0.8.1** the Android implementation support to pick -(multiple) images on Android 4.3 or higher. - No configuration required - the plugin should work out of the box. It is however highly recommended to prepare for Android killing the application when low on memory. How to prepare for this is discussed in the [Handling MainActivity destruction on Android](#handling-mainactivity-destruction-on-android) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 6bf6715bf77..b52ee9797e7 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.8.13+1 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates minimum supported SDK version to Flutter 3.35. +* Removes obsolete code related to supporting SDK <24. ## 0.8.13 diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index fe937ef4e26..5f67ae3bfc0 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -26,7 +26,7 @@ android { compileSdk = flutter.compileSdkVersion defaultConfig { - minSdkVersion 21 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index c76d6b63670..d3317645f90 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -988,14 +988,10 @@ private void finishWithError(String errorCode, String errorMessage) { } private void useFrontCamera(Intent intent) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { intent.putExtra( "android.intent.extras.CAMERA_FACING", CameraCharacteristics.LENS_FACING_FRONT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); } - } else { - intent.putExtra("android.intent.extras.CAMERA_FACING", 1); - } } } diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java index d14056423c4..60b20c5a772 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java @@ -45,8 +45,8 @@ private static PackageInfo getPermissionsPackageInfoPreApi33( } /** - * Camera permission need request if it present in manifest, because for M or great for take Photo - * ar Video by intent need it permission, even if the camera permission is not used. + * Camera permission needs to be requested if it is present in the manifest, even if the camera + * permission is not used. * *

Camera permission may be used in another package, as example flutter_barcode_reader. * https://github.com/flutter/flutter/issues/29837 @@ -54,8 +54,7 @@ private static PackageInfo getPermissionsPackageInfoPreApi33( * @return returns true, if need request camera permission, otherwise false */ static boolean needRequestCameraPermission(Context context) { - boolean greatOrEqualM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - return greatOrEqualM && isPermissionPresentInManifest(context, Manifest.permission.CAMERA); + return isPermissionPresentInManifest(context, Manifest.permission.CAMERA); } /** diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index 2e950379b87..4dbd12f2e3b 100644 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the image_picker plugin. publish_to: none environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index d2a1505d064..a753ffa5268 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,11 +2,11 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.13 +version: 0.8.13+1 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" flutter: plugin: From 1a6bde392e8621588cba551bbfaf79a8004c074b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 13:07:22 -0400 Subject: [PATCH 04/12] Scrub url_launcher --- .../url_launcher/url_launcher_android/CHANGELOG.md | 5 +++-- .../url_launcher_android/android/build.gradle | 2 +- .../plugins/urllauncher/WebViewActivity.java | 14 -------------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index aed3b6e9d8b..8ebc744a716 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 6.3.18 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates minimum supported SDK version to Flutter 3.35. +* Removes obsolete code related to supporting SDK <24. ## 6.3.17 diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index af15b5bd047..b4690b789d0 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/android/build.gradle @@ -29,7 +29,7 @@ android { compileSdk = flutter.compileSdkVersion defaultConfig { - minSdkVersion 21 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java index 3ae49cece79..026e86a4fc1 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java @@ -9,7 +9,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Build; import android.os.Bundle; import android.os.Message; import android.provider.Browser; @@ -20,7 +19,6 @@ import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; import java.util.Collections; @@ -49,7 +47,6 @@ public void onReceive(Context context, Intent intent) { private final WebViewClient webViewClient = new WebViewClient() { - @RequiresApi(Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { view.loadUrl(request.getUrl().toString()); @@ -75,17 +72,6 @@ public boolean shouldOverrideUrlLoading( webview.loadUrl(request.getUrl().toString()); return true; } - - /* - * This method is deprecated in API 24. Still overridden to support - * earlier Android versions. - */ - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - webview.loadUrl(url); - return true; - } }; final WebView newWebView = new WebView(webview.getContext()); From 3e53ceb2b7e2ef368ffaf5425853a28c0d6bfe23 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 13:24:34 -0400 Subject: [PATCH 05/12] Scrub webview_flutter --- .../webview_flutter/webview_flutter/README.md | 7 +- .../webview_flutter_android/CHANGELOG.md | 5 +- .../webview_flutter_android/README.md | 8 +- .../android/build.gradle | 2 +- .../webviewflutter/AndroidWebkitLibrary.g.kt | 12 +- .../webviewflutter/ProxyApiRegistrar.java | 2 - .../WebChromeClientProxyApi.java | 13 - .../WebResourceErrorProxyApi.java | 3 - .../WebResourceRequestProxyApi.java | 7 +- .../webviewflutter/WebViewClientProxyApi.java | 216 +------------- .../WebViewClientCompatTest.java | 271 ------------------ .../example/pubspec.yaml | 4 +- .../lib/src/android_webkit.g.dart | 6 +- .../lib/src/android_webview_controller.dart | 5 +- .../pigeons/android_webkit.dart | 1 - .../webview_flutter_android/pubspec.yaml | 6 +- 16 files changed, 27 insertions(+), 541 deletions(-) delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatTest.java diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index 8bd3e087015..5c0d2b8aaf3 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -11,7 +11,7 @@ On Android the WebView widget is backed by a [WebView](https://developer.android | | Android | iOS | macOS | |-------------|---------|-------|--------| -| **Support** | SDK 21+ | 12.0+ | 10.14+ | +| **Support** | SDK 24+ | 12.0+ | 10.14+ | ## Usage @@ -160,10 +160,9 @@ for more details. ### PlatformView Implementation on Android -The PlatformView implementation for Android uses Texture Layer Hybrid Composition on versions 23+ -and automatically fallbacks to Hybrid Composition for version 21-23. See section +The PlatformView implementation for Android uses Texture Layer Hybrid Composition. See section `Platform-Specific Features` and [AndroidWebViewWidgetCreationParams.displayWithHybridComposition](https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/AndroidWebViewWidgetCreationParams/displayWithHybridComposition.html) -to manually switch to Hybrid Composition on versions 23+. +to manually switch to Hybrid Composition. ### API Changes diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 244af90687d..70482d9bbeb 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 4.10.1 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates minimum supported SDK version to Flutter 3.35. +* Removes obsolete code related to supporting SDK <24. ## 4.10.0 diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md index 10cc91b2bae..a87a4ace48f 100644 --- a/packages/webview_flutter/webview_flutter_android/README.md +++ b/packages/webview_flutter/webview_flutter_android/README.md @@ -19,7 +19,7 @@ specific mode, you can set it explicitly. ### Texture Layer Hybrid Composition -This is the current default mode for versions >=23. This is a new display mode used by most +This is the current default mode, and is the display mode used by most plugins starting with Flutter 3.0. This is more performant than Hybrid Composition, but has some limitations from using an Android [SurfaceTexture](https://developer.android.com/reference/android/graphics/SurfaceTexture). See: @@ -28,11 +28,11 @@ See: ### Hybrid Composition -This is the current default mode for versions <23. It ensures that the WebView will display and work -as expected, at the cost of some performance. See: +This ensures that the WebView will display and work as expected in the edge cases noted above, at +the cost of some performance. See: * https://docs.flutter.dev/platform-integration/android/platform-views#performance -This can be configured for versions >=23 with +This can be configured with `AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features for more details on setting platform-specific features in the main plugin. diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index b2d1c45296e..8e1ca2a7ad8 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -38,7 +38,7 @@ android { } defaultConfig { - minSdkVersion 21 + minSdkVersion 24 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } 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 3a9932d3f4f..09e91dfefb5 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 @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -127,10 +127,6 @@ class AndroidWebkitLibraryPigeonInstanceManager( */ fun remove(identifier: Long): T? { logWarningIfFinalizationListenerHasStopped() - val instance: Any? = getInstance(identifier) - if (instance is WebViewProxyApi.WebViewPlatformView) { - instance.destroy() - } return strongInstances.remove(identifier) as T? } @@ -687,7 +683,7 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec( registrar.getPigeonApiWebResourceRequest().pigeon_newInstance(value) {} } else if (value is android.webkit.WebResourceResponse) { registrar.getPigeonApiWebResourceResponse().pigeon_newInstance(value) {} - } else if (android.os.Build.VERSION.SDK_INT >= 23 && value is android.webkit.WebResourceError) { + } else if (value is android.webkit.WebResourceError) { registrar.getPigeonApiWebResourceError().pigeon_newInstance(value) {} } else if (value is androidx.webkit.WebResourceErrorCompat) { registrar.getPigeonApiWebResourceErrorCompat().pigeon_newInstance(value) {} @@ -1124,16 +1120,13 @@ abstract class PigeonApiWebResourceError( open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar ) { /** The error code of the error. */ - @androidx.annotation.RequiresApi(api = 23) abstract fun errorCode(pigeon_instance: android.webkit.WebResourceError): Long /** The string describing the error. */ - @androidx.annotation.RequiresApi(api = 23) abstract fun description(pigeon_instance: android.webkit.WebResourceError): String @Suppress("LocalVariableName", "FunctionName") /** Creates a Dart instance of WebResourceError and attaches it to [pigeon_instanceArg]. */ - @androidx.annotation.RequiresApi(api = 23) fun pigeon_newInstance( pigeon_instanceArg: android.webkit.WebResourceError, callback: (Result) -> Unit @@ -3112,7 +3105,6 @@ abstract class PigeonApiWebViewClient( } /** Report web resource loading error to the host application. */ - @androidx.annotation.RequiresApi(api = 23) fun onReceivedRequestError( pigeon_instanceArg: android.webkit.WebViewClient, webViewArg: android.webkit.WebView, 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 84e025a7a0c..ce406a578de 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 @@ -12,7 +12,6 @@ import android.util.Log; import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import io.flutter.plugin.common.BinaryMessenger; public class ProxyApiRegistrar extends AndroidWebkitLibraryPigeonProxyApiRegistrar { @@ -75,7 +74,6 @@ public PigeonApiWebResourceRequest getPigeonApiWebResourceRequest() { return new WebResourceRequestProxyApi(this); } - @RequiresApi(api = Build.VERSION_CODES.M) @NonNull @Override public PigeonApiWebResourceError getPigeonApiWebResourceError() { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java index 8a0d86f7b45..f86bf5bd586 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java @@ -5,7 +5,6 @@ package io.flutter.plugins.webviewflutter; import android.net.Uri; -import android.os.Build; import android.os.Message; import android.view.View; import android.webkit.ConsoleMessage; @@ -20,7 +19,6 @@ import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import java.util.List; import java.util.Objects; @@ -278,7 +276,6 @@ boolean onCreateWindow( final WebViewClient windowWebViewClient = new WebViewClient() { - @RequiresApi(api = Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading( @NonNull WebView windowWebView, @NonNull WebResourceRequest request) { @@ -287,16 +284,6 @@ public boolean shouldOverrideUrlLoading( } return true; } - - // Legacy codepath for < N. - @Override - @SuppressWarnings({"deprecation", "RedundantSuppression"}) - public boolean shouldOverrideUrlLoading(WebView windowWebView, String url) { - if (!webViewClient.shouldOverrideUrlLoading(view, url)) { - view.loadUrl(url); - } - return true; - } }; if (onCreateWindowWebView == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java index 88baba36e16..9c258e3de2c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java @@ -4,12 +4,9 @@ package io.flutter.plugins.webviewflutter; -import android.os.Build; import android.webkit.WebResourceError; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -@RequiresApi(api = Build.VERSION_CODES.M) public class WebResourceErrorProxyApi extends PigeonApiWebResourceError { public WebResourceErrorProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { super(pigeonRegistrar); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java index 872cf27d2d1..47c2c30f1bd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java @@ -4,7 +4,6 @@ package io.flutter.plugins.webviewflutter; -import android.os.Build; import android.webkit.WebResourceRequest; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,11 +30,7 @@ public boolean isForMainFrame(@NonNull WebResourceRequest pigeon_instance) { @Nullable @Override public Boolean isRedirect(@NonNull WebResourceRequest pigeon_instance) { - if (getPigeonRegistrar().sdkIsAtLeast(Build.VERSION_CODES.N)) { - return pigeon_instance.isRedirect(); - } - - return null; + return pigeon_instance.isRedirect(); } @NonNull diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java index d5fd065ac86..e02517c138e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java @@ -5,7 +5,6 @@ package io.flutter.plugins.webviewflutter; import android.graphics.Bitmap; -import android.os.Build; import android.view.KeyEvent; import android.webkit.HttpAuthHandler; import android.webkit.WebResourceError; @@ -15,9 +14,6 @@ import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.webkit.WebResourceErrorCompat; -import androidx.webkit.WebViewClientCompat; /** * Host api implementation for {@link WebViewClient}. @@ -26,7 +22,6 @@ */ public class WebViewClientProxyApi extends PigeonApiWebViewClient { /** Implementation of {@link WebViewClient} that passes arguments of callback methods to Dart. */ - @RequiresApi(Build.VERSION_CODES.N) public static class WebViewClientImpl extends WebViewClient { private final WebViewClientProxyApi api; private boolean returnValueForShouldOverrideUrlLoading = false; @@ -72,21 +67,6 @@ public void onReceivedError( () -> api.onReceivedRequestError(this, view, request, error, reply -> null)); } - // Legacy codepath for < 23; newer versions use the variant above. - @SuppressWarnings("deprecation") - @Override - public void onReceivedError( - @NonNull WebView view, - int errorCode, - @NonNull String description, - @NonNull String failingUrl) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> - api.onReceivedError( - this, view, (long) errorCode, description, failingUrl, reply -> null)); - } - @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { @@ -98,15 +78,6 @@ public boolean shouldOverrideUrlLoading( return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; } - // Legacy codepath for < 24; newer versions use the variant above. - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull String url) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.urlLoading(this, view, url, reply -> null)); - return returnValueForShouldOverrideUrlLoading; - } - @Override public void doUpdateVisitedHistory( @NonNull WebView view, @NonNull String url, boolean isReload) { @@ -196,174 +167,6 @@ public void setReturnValueForShouldOverrideUrlLoading(boolean value) { } } - /** - * Implementation of {@link WebViewClientCompat} that passes arguments of callback methods to - * Dart. - */ - public static class WebViewClientCompatImpl extends WebViewClientCompat { - private final WebViewClientProxyApi api; - private boolean returnValueForShouldOverrideUrlLoading = false; - - public WebViewClientCompatImpl(@NonNull WebViewClientProxyApi api) { - this.api = api; - } - - @Override - public void onPageStarted(@NonNull WebView view, @NonNull String url, @NonNull Bitmap favicon) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onPageStarted(this, view, url, reply -> null)); - } - - @Override - public void onPageFinished(@NonNull WebView view, @NonNull String url) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onPageFinished(this, view, url, reply -> null)); - } - - @Override - public void onReceivedHttpError( - @NonNull WebView view, - @NonNull WebResourceRequest request, - @NonNull WebResourceResponse response) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onReceivedHttpError(this, view, request, response, reply -> null)); - } - - @Override - public void onReceivedError( - @NonNull WebView view, - @NonNull WebResourceRequest request, - @NonNull WebResourceErrorCompat error) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onReceivedRequestErrorCompat(this, view, request, error, reply -> null)); - } - - // Legacy codepath for versions that don't support the variant above. - @SuppressWarnings("deprecation") - @Override - public void onReceivedError( - @NonNull WebView view, - int errorCode, - @NonNull String description, - @NonNull String failingUrl) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> - api.onReceivedError( - this, view, (long) errorCode, description, failingUrl, reply -> null)); - } - - @Override - public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.requestLoading(this, view, request, reply -> null)); - - // The client is only allowed to stop navigations that target the main frame because - // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. - return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; - } - - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(@NonNull WebView view, @NonNull String url) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.urlLoading(this, view, url, reply -> null)); - return returnValueForShouldOverrideUrlLoading; - } - - @Override - public void doUpdateVisitedHistory( - @NonNull WebView view, @NonNull String url, boolean isReload) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.doUpdateVisitedHistory(this, view, url, isReload, reply -> null)); - } - - // Handles an HTTP authentication request. - // - // This callback is invoked when the WebView encounters a website requiring HTTP authentication. - // [host] and [realm] are provided for matching against stored credentials, if any. - @Override - public void onReceivedHttpAuthRequest( - @NonNull WebView view, HttpAuthHandler handler, String host, String realm) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onReceivedHttpAuthRequest(this, view, handler, host, realm, reply -> null)); - } - - @Override - public void onFormResubmission( - @NonNull android.webkit.WebView view, - @NonNull android.os.Message dontResend, - @NonNull android.os.Message resend) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onFormResubmission(this, view, dontResend, resend, reply -> null)); - } - - @Override - public void onLoadResource(@NonNull android.webkit.WebView view, @NonNull String url) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onLoadResource(this, view, url, reply -> null)); - } - - @Override - public void onPageCommitVisible(@NonNull android.webkit.WebView view, @NonNull String url) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onPageCommitVisible(this, view, url, reply -> null)); - } - - @Override - public void onReceivedClientCertRequest( - @NonNull android.webkit.WebView view, @NonNull android.webkit.ClientCertRequest request) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onReceivedClientCertRequest(this, view, request, reply -> null)); - } - - @Override - public void onReceivedLoginRequest( - @NonNull android.webkit.WebView view, - @NonNull String realm, - @Nullable String account, - @NonNull String args) { - api.getPigeonRegistrar() - .runOnMainThread( - () -> api.onReceivedLoginRequest(this, view, realm, account, args, reply -> null)); - } - - @Override - public void onReceivedSslError( - @NonNull android.webkit.WebView view, - @NonNull android.webkit.SslErrorHandler handler, - @NonNull android.net.http.SslError error) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onReceivedSslError(this, view, handler, error, reply -> null)); - } - - @Override - public void onScaleChanged( - @NonNull android.webkit.WebView view, float oldScale, float newScale) { - api.getPigeonRegistrar() - .runOnMainThread(() -> api.onScaleChanged(this, view, oldScale, newScale, reply -> null)); - } - - @Override - public void onUnhandledKeyEvent(@NonNull WebView view, @NonNull KeyEvent event) { - // Deliberately empty. Occasionally the webview will mark events as having failed to be - // handled even though they were handled. We don't want to propagate those as they're not - // truly lost. - } - - /** Sets return value for {@link #shouldOverrideUrlLoading}. */ - public void setReturnValueForShouldOverrideUrlLoading(boolean value) { - returnValueForShouldOverrideUrlLoading = value; - } - } - /** Creates a host API that handles creating {@link WebViewClient}s. */ public WebViewClientProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { super(pigeonRegistrar); @@ -372,28 +175,13 @@ public WebViewClientProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { @NonNull @Override public WebViewClient pigeon_defaultConstructor() { - // WebViewClientCompat is used to get - // shouldOverrideUrlLoading(WebView view, WebResourceRequest request) - // invoked by the webview on older Android devices, without it pages that use iframes will - // be broken when a navigationDelegate is set on Android version earlier than N. - // - // However, this if statement attempts to avoid using WebViewClientCompat on versions >= N due - // to bug https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see - // https://github.com/flutter/flutter/issues/29446. - if (getPigeonRegistrar().sdkIsAtLeast(Build.VERSION_CODES.N)) { - return new WebViewClientImpl(this); - } else { - return new WebViewClientCompatImpl(this); - } + return new WebViewClientImpl(this); } @Override public void setSynchronousReturnValueForShouldOverrideUrlLoading( @NonNull WebViewClient pigeon_instance, boolean value) { - if (pigeon_instance instanceof WebViewClientCompatImpl) { - ((WebViewClientCompatImpl) pigeon_instance).setReturnValueForShouldOverrideUrlLoading(value); - } else if (getPigeonRegistrar().sdkIsAtLeast(Build.VERSION_CODES.N) - && pigeon_instance instanceof WebViewClientImpl) { + if (pigeon_instance instanceof WebViewClientImpl) { ((WebViewClientImpl) pigeon_instance).setReturnValueForShouldOverrideUrlLoading(value); } else { throw new IllegalStateException( diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatTest.java deleted file mode 100644 index 07c1bd41f00..00000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientCompatTest.java +++ /dev/null @@ -1,271 +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 static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.net.http.SslError; -import android.os.Message; -import android.webkit.ClientCertRequest; -import android.webkit.HttpAuthHandler; -import android.webkit.SslErrorHandler; -import android.webkit.WebResourceRequest; -import android.webkit.WebView; -import org.junit.Test; - -public class WebViewClientCompatTest { - @Test - public void onPageStarted() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final WebView webView = mock(WebView.class); - final String url = "myString"; - instance.onPageStarted(webView, url, null); - - verify(mockApi).onPageStarted(eq(instance), eq(webView), eq(url), any()); - } - - @Test - public void onReceivedError() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final android.webkit.WebView webView = mock(WebView.class); - final Long errorCode = 0L; - final String description = "myString"; - final String failingUrl = "myString1"; - instance.onReceivedError(webView, errorCode.intValue(), description, failingUrl); - - verify(mockApi) - .onReceivedError( - eq(instance), eq(webView), eq(errorCode), eq(description), eq(failingUrl), any()); - } - - @Test - public void urlLoading() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - instance.shouldOverrideUrlLoading(webView, url); - - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); - } - - @Test - public void urlLoadingForMainFrame() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - instance.setReturnValueForShouldOverrideUrlLoading(false); - final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - instance.shouldOverrideUrlLoading(webView, url); - - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); - } - - @Test - public void urlLoadingForMainFrameWithOverride() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - instance.setReturnValueForShouldOverrideUrlLoading(true); - final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - - assertTrue(instance.shouldOverrideUrlLoading(webView, url)); - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); - } - - @Test - public void urlLoadingNotForMainFrame() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final android.webkit.WebView webView = mock(WebView.class); - final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); - when(request.isForMainFrame()).thenReturn(false); - instance.shouldOverrideUrlLoading(webView, request); - - verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); - } - - @Test - public void urlLoadingNotForMainFrameWithOverride() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - instance.setReturnValueForShouldOverrideUrlLoading(true); - - final android.webkit.WebView webView = mock(WebView.class); - final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); - when(request.isForMainFrame()).thenReturn(false); - - assertFalse(instance.shouldOverrideUrlLoading(webView, request)); - verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); - } - - @Test - public void doUpdateVisitedHistory() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - final Boolean isReload = true; - instance.doUpdateVisitedHistory(webView, url, isReload); - - verify(mockApi).doUpdateVisitedHistory(eq(instance), eq(webView), eq(url), eq(isReload), any()); - } - - @Test - public void onReceivedHttpError() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientCompatImpl instance = - new WebViewClientProxyApi.WebViewClientCompatImpl(mockApi); - final android.webkit.WebView webView = mock(WebView.class); - final HttpAuthHandler handler = mock(HttpAuthHandler.class); - final String host = "myString"; - final String realm = "myString1"; - instance.onReceivedHttpAuthRequest(webView, handler, host, realm); - - verify(mockApi) - .onReceivedHttpAuthRequest( - eq(instance), eq(webView), eq(handler), eq(host), eq(realm), any()); - } - - @Test - public void onFormResubmission() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final android.os.Message dontResend = mock(Message.class); - final android.os.Message resend = mock(Message.class); - instance.onFormResubmission(view, dontResend, resend); - - verify(mockApi).onFormResubmission(eq(instance), eq(view), eq(dontResend), eq(resend), any()); - } - - @Test - public void onLoadResource() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final String url = "myString"; - instance.onLoadResource(view, url); - - verify(mockApi).onLoadResource(eq(instance), eq(view), eq(url), any()); - } - - @Test - public void onPageCommitVisible() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final String url = "myString"; - instance.onPageCommitVisible(view, url); - - verify(mockApi).onPageCommitVisible(eq(instance), eq(view), eq(url), any()); - } - - @Test - public void onReceivedClientCertRequest() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final android.webkit.ClientCertRequest request = mock(ClientCertRequest.class); - instance.onReceivedClientCertRequest(view, request); - - verify(mockApi).onReceivedClientCertRequest(eq(instance), eq(view), eq(request), any()); - } - - @Test - public void onReceivedLoginRequest() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final String realm = "myString"; - final String account = "myString1"; - final String args = "myString2"; - instance.onReceivedLoginRequest(view, realm, account, args); - - verify(mockApi) - .onReceivedLoginRequest(eq(instance), eq(view), eq(realm), eq(account), eq(args), any()); - } - - @Test - public void onReceivedSslError() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final android.webkit.SslErrorHandler handler = mock(SslErrorHandler.class); - final android.net.http.SslError error = mock(SslError.class); - instance.onReceivedSslError(view, handler, error); - - verify(mockApi).onReceivedSslError(eq(instance), eq(view), eq(handler), eq(error), any()); - } - - @Test - public void onScaleChanged() { - final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); - when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); - - final WebViewClientProxyApi.WebViewClientImpl instance = - new WebViewClientProxyApi.WebViewClientImpl(mockApi); - final android.webkit.WebView view = mock(WebView.class); - final float oldScale = 1.0f; - final float newScale = 2.0f; - instance.onScaleChanged(view, oldScale, newScale); - - verify(mockApi) - .onScaleChanged( - eq(instance), eq(view), eq((double) oldScale), eq((double) newScale), any()); - } -} diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 93141e0cfce..e5a64318747 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the webview_flutter_android plugin. publish_to: none environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" dependencies: flutter: 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 0117cc86e63..1db58a68041 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 @@ -1,11 +1,12 @@ // 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. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; +import 'dart:io' show Platform; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' @@ -128,6 +129,9 @@ class PigeonInstanceManager { late final void Function(int) onWeakReferenceRemoved; static PigeonInstanceManager _initInstance() { + if (Platform.environment['FLUTTER_TEST'] == 'true') { + return PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + } WidgetsFlutterBinding.ensureInitialized(); final _PigeonInternalInstanceManagerApi api = _PigeonInternalInstanceManagerApi(); 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 e4a686dbe79..bcbecdd0b89 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 @@ -712,8 +712,6 @@ class AndroidWebViewController extends PlatformWebViewController { /// Sets a callback that notifies the host application that web content is /// requesting permission to access the specified resources. - /// - /// Only invoked on Android versions 21+. @override Future setOnPlatformPermissionRequest( void Function(PlatformWebViewPermissionRequest request) onPermissionRequest, @@ -729,8 +727,7 @@ class AndroidWebViewController extends PlatformWebViewController { /// The host application should invoke the specified callback with the desired permission state. /// See GeolocationPermissions for details. /// - /// Note that for applications targeting Android N and later SDKs (API level > Build.VERSION_CODES.M) - /// this method is only called for requests originating from secure origins such as https. + /// This method is only called for requests originating from secure origins such as https. /// On non-secure origins geolocation requests are automatically denied. /// /// Param [onHidePrompt] notifies the host application that a request for Geolocation permissions, 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 c8dc41a8cb4..afa081ce129 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -189,7 +189,6 @@ abstract class WebResourceResponse { @ProxyApi( kotlinOptions: KotlinProxyApiOptions( fullClassName: 'android.webkit.WebResourceError', - minAndroidApi: 23, ), ) abstract class WebResourceError { diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 444bcf615ba..d20b4894f35 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,11 +2,11 @@ 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.10.0 +version: 4.10.1 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" flutter: plugin: From 0cce85a26dbc71ae7945f0d74f621d36b7fc9b00 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 13:39:51 -0400 Subject: [PATCH 06/12] CHANGELOGs --- packages/camera/camera/CHANGELOG.md | 1 + packages/image_picker/image_picker/CHANGELOG.md | 1 + packages/local_auth/local_auth/CHANGELOG.md | 2 +- packages/webview_flutter/webview_flutter/CHANGELOG.md | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 0051c4f7652..fae21094cd7 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates README to reflect that only Android API 24+ is supported. ## 0.11.2 diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 218c09bca86..8f6efb51383 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates README to reflect that only Android API 24+ is supported. ## 1.2.0 diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index 9dd19517d5c..d8cf7652a76 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,7 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. -* Updates README to indicate that Andoid SDK <21 is no longer supported. +* Updates README to reflect that only Android API 24+ is supported. ## 2.3.0 diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 237a1b3f7f8..fad345bad30 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +* Updates README to reflect that only Android API 24+ is supported. ## 4.13.0 From 6ad8f1e770a51df2df88adc55511cddab171f01c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 19 Aug 2025 13:46:07 -0400 Subject: [PATCH 07/12] Missed autoformat --- .../flutter/plugins/imagepicker/ImagePickerDelegate.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index d3317645f90..7262377f481 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -988,10 +988,9 @@ private void finishWithError(String errorCode, String errorMessage) { } private void useFrontCamera(Intent intent) { - intent.putExtra( - "android.intent.extras.CAMERA_FACING", CameraCharacteristics.LENS_FACING_FRONT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); - } + intent.putExtra("android.intent.extras.CAMERA_FACING", CameraCharacteristics.LENS_FACING_FRONT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); + } } } From 998d7feadbc976313811b4c16016adc3647957c4 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 27 Aug 2025 16:51:52 -0400 Subject: [PATCH 08/12] Clean up obsolete test in local_auth --- .../java/io/flutter/plugins/localauth/LocalAuthTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java index 1fbc20a7803..0ebaf7a89b3 100644 --- a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java +++ b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java @@ -341,14 +341,6 @@ public void getEnrolledBiometrics_shouldAddStrongBiometrics() { } @Test - @Config(sdk = 22) - public void isDeviceSecure_returnsFalseOnBelowApi23() { - final LocalAuthPlugin plugin = new LocalAuthPlugin(); - assertFalse(plugin.isDeviceSecure()); - } - - @Test - @Config(sdk = 23) public void isDeviceSecure_returnsTrueIfDeviceIsSecure() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); From 77aa18eda79a92a57b7f6ab25ec985fff9fdc4ef Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 28 Aug 2025 10:56:20 -0400 Subject: [PATCH 09/12] Remove deprecated onReceivedError --- .../webviewflutter/AndroidWebkitLibrary.g.kt | 36 -- .../webviewflutter/WebViewClientTest.java | 32 +- .../webview_flutter_test.dart | 44 +- .../webview_flutter_test_legacy.dart | 13 +- .../example/lib/legacy/web_view.dart | 42 +- .../example/lib/main.dart | 151 +++--- .../lib/src/android_proxy.dart | 1 - .../lib/src/android_ssl_auth_error.dart | 9 +- .../lib/src/android_webkit.g.dart | 120 +---- .../lib/src/android_webview_controller.dart | 472 ++++++++--------- .../src/android_webview_cookie_manager.dart | 10 +- .../src/legacy/webview_android_widget.dart | 65 +-- .../src/legacy/webview_surface_android.dart | 27 +- .../pigeons/android_webkit.dart | 9 - .../android_navigation_delegate_test.dart | 24 - .../test/android_webview_controller_test.dart | 480 +++++++++--------- ...android_webview_controller_test.mocks.dart | 115 ++--- .../android_webview_cookie_manager_test.dart | 9 +- ...oid_webview_cookie_manager_test.mocks.dart | 9 + .../webview_android_cookie_manager_test.dart | 19 +- .../legacy/webview_android_widget_test.dart | 63 --- .../webview_android_widget_test.mocks.dart | 4 - 22 files changed, 723 insertions(+), 1031 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 09e91dfefb5..4c31072c71a 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 @@ -3172,42 +3172,6 @@ abstract class PigeonApiWebViewClient( } } - /** Report an error to the host application. */ - fun onReceivedError( - pigeon_instanceArg: android.webkit.WebViewClient, - webViewArg: android.webkit.WebView, - errorCodeArg: Long, - descriptionArg: String, - failingUrlArg: String, - callback: (Result) -> Unit - ) { - if (pigeonRegistrar.ignoreCallsToDart) { - callback( - Result.failure( - AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) - return - } - val binaryMessenger = pigeonRegistrar.binaryMessenger - val codec = pigeonRegistrar.codec - val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedError" - val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send( - listOf(pigeon_instanceArg, webViewArg, errorCodeArg, descriptionArg, failingUrlArg)) { - 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))) - } - } - } - /** * Give the host application a chance to take control when a URL is about to be loaded in the * current WebView. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java index 9f8bb4ea295..a060b56234e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -17,6 +17,7 @@ import android.webkit.ClientCertRequest; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; +import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebView; import io.flutter.plugins.webviewflutter.WebViewClientProxyApi.WebViewClientImpl; @@ -43,14 +44,11 @@ public void onReceivedError() { final WebViewClientImpl instance = new WebViewClientImpl(mockApi); final android.webkit.WebView webView = mock(WebView.class); - final Long errorCode = 0L; - final String description = "myString"; - final String failingUrl = "myString1"; - instance.onReceivedError(webView, errorCode.intValue(), description, failingUrl); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + final android.webkit.WebResourceError error = mock(WebResourceError.class); + instance.onReceivedError(webView, request, error); - verify(mockApi) - .onReceivedError( - eq(instance), eq(webView), eq(errorCode), eq(description), eq(failingUrl), any()); + verify(mockApi).onReceivedError(eq(instance), eq(webView), eq(request), eq(error), any()); } @Test @@ -60,10 +58,10 @@ public void urlLoading() { final WebViewClientImpl instance = new WebViewClientImpl(mockApi); final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - instance.shouldOverrideUrlLoading(webView, url); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + instance.shouldOverrideUrlLoading(webView, request); - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); } @Test @@ -74,10 +72,11 @@ public void urlLoadingForMainFrame() { final WebViewClientImpl instance = new WebViewClientImpl(mockApi); instance.setReturnValueForShouldOverrideUrlLoading(false); final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; - instance.shouldOverrideUrlLoading(webView, url); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(true); + instance.shouldOverrideUrlLoading(webView, request); - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); } @Test @@ -88,10 +87,11 @@ public void urlLoadingForMainFrameWithOverride() { final WebViewClientImpl instance = new WebViewClientImpl(mockApi); instance.setReturnValueForShouldOverrideUrlLoading(true); final android.webkit.WebView webView = mock(WebView.class); - final String url = "myString"; + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(true); - assertTrue(instance.shouldOverrideUrlLoading(webView, url)); - verify(mockApi).urlLoading(eq(instance), eq(webView), eq(url), any()); + assertTrue(instance.shouldOverrideUrlLoading(webView, request)); + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 30231c619c4..fedcd4ca99b 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -139,23 +139,24 @@ Future main() async { controller: PlatformWebViewController( AndroidWebViewControllerCreationParams( androidWebViewProxy: AndroidWebViewProxy( - newWebView: ({ - void Function( - android_webkit.WebView, - int, - int, - int, - int, - )? - onScrollChanged, - }) { - final android_webkit.WebView webView = - android_webkit.WebView( - onScrollChanged: onScrollChanged, - ); - finalizer.attach(webView, webViewToken); - return webView; - }, + newWebView: + ({ + void Function( + android_webkit.WebView, + int, + int, + int, + int, + )? + onScrollChanged, + }) { + final android_webkit.WebView webView = + android_webkit.WebView( + onScrollChanged: onScrollChanged, + ); + finalizer.attach(webView, webViewToken); + return webView; + }, ), ), ), @@ -387,7 +388,8 @@ Future main() async { final String base64VideoData = base64Encode( Uint8List.view(videoData.buffer), ); - final String videoTest = ''' + final String videoTest = + ''' Video auto play