From 166e2c270978165250453786bb0b337ac24d54fd Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sat, 15 Jul 2023 07:06:33 -0400 Subject: [PATCH] [ci] Switch Android unit tests to LUCI (#4406) This moves Android unit tests from Cirrus to LUCI. In order to accomplish this: - Switches the Android LUCI bots from JDK 11 to JDK 12, to resolve a crash when compiling `camera_android` unit tests with 11. - Adds wrappers to SDK checks where necessary for testability, since the hack to override `Build.VERSION.SDK_INT` in unit tests (which was already giving warnings when run with JDK 11) no longer works at all in JDK 12. Part of https://github.com/flutter/flutter/issues/114373 --- .ci.yaml | 12 +++- .ci/targets/android_platform_tests.yaml | 8 +-- .cirrus.yml | 4 -- packages/camera/camera_android/CHANGELOG.md | 4 ++ .../io/flutter/plugins/camera/Camera.java | 14 ++--- .../plugins/camera/CameraCaptureCallback.java | 12 +++- .../plugins/camera/CameraRegionUtils.java | 2 +- .../io/flutter/plugins/camera/DeviceInfo.java | 24 ++++++++ .../plugins/camera/SdkCapabilityChecker.java | 60 +++++++++++++++++++ .../flutter/plugins/camera/VideoRenderer.java | 2 +- .../features/fpsrange/FpsRangeFeature.java | 6 +- .../noisereduction/NoiseReductionFeature.java | 5 +- .../resolution/ResolutionFeature.java | 5 +- .../features/zoomlevel/ZoomLevelFeature.java | 6 +- .../camera/media/MediaRecorderBuilder.java | 4 +- .../CameraCaptureCallbackStatesTest.java | 20 ++----- ...raRegionUtils_getCameraBoundariesTest.java | 3 +- .../io/flutter/plugins/camera/CameraTest.java | 49 +++++++++------ .../fpsrange/FpsRangeFeaturePixel4aTest.java | 7 +-- .../fpsrange/FpsRangeFeatureTest.java | 11 ++-- .../NoiseReductionFeatureTest.java | 11 ++-- .../zoomlevel/ZoomLevelFeatureTest.java | 14 ++--- .../plugins/camera/utils/TestUtils.java | 16 ----- packages/camera/camera_android/pubspec.yaml | 2 +- .../file_selector_android/CHANGELOG.md | 4 ++ .../FileSelectorApiImpl.java | 28 +++++++-- .../FileSelectorAndroidPluginTest.java | 49 ++++++++------- .../file_selector_android/pubspec.yaml | 2 +- .../quick_actions_android/CHANGELOG.md | 4 ++ .../quickactions/QuickActionsPlugin.java | 21 ++++++- .../quickactions/QuickActionsTest.java | 36 +++-------- .../quick_actions_android/pubspec.yaml | 2 +- .../webview_flutter_android/CHANGELOG.md | 4 ++ .../CookieManagerHostApiImpl.java | 34 +++++++---- .../webviewflutter/WebViewHostApiImpl.java | 26 +++++++- .../webviewflutter/CookieManagerTest.java | 17 +++--- .../plugins/webviewflutter/WebViewTest.java | 8 ++- .../webviewflutter/utils/TestUtils.java | 26 -------- .../webview_flutter_android/pubspec.yaml | 2 +- 39 files changed, 343 insertions(+), 221 deletions(-) create mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DeviceInfo.java create mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java delete mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java diff --git a/.ci.yaml b/.ci.yaml index 9fd29c7c1d9b..8702a4769748 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -26,7 +26,7 @@ platform_properties: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "curl", "version": "version:7.64.0"} ] linux_desktop: @@ -286,6 +286,11 @@ targets: version_file: flutter_master.version target_file: android_build_all_packages.yaml channel: master + # The legacy project build requires an older JDK. + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:11"} + ] - name: Linux_android android_build_all_packages stable recipe: packages/packages @@ -295,6 +300,11 @@ targets: version_file: flutter_stable.version target_file: android_build_all_packages.yaml channel: stable + # The legacy project build requires an older JDK. + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:11"} + ] - name: Linux_android android_platform_tests_shard_1 master recipe: packages/packages diff --git a/.ci/targets/android_platform_tests.yaml b/.ci/targets/android_platform_tests.yaml index 40dbc7d2d017..70ca7b1adcba 100644 --- a/.ci/targets/android_platform_tests.yaml +++ b/.ci/targets/android_platform_tests.yaml @@ -16,11 +16,9 @@ tasks: # different exclusions. # TODO(stuartmorgan): Eliminate the native unit test exclusion, and combine # these steps. - # TODO(stuartmorgan): Enable this once https://github.com/flutter/flutter/issues/130148 - # is resolved. - #- name: native unit tests - # script: script/tool_runner.sh - # args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml"] + - name: native unit tests + script: script/tool_runner.sh + args: ["native-test", "--android", "--no-integration", "--exclude=script/configs/exclude_native_unit_android.yaml"] # TODO(stuartmorgan): Enable these once # https://github.com/flutter/flutter/issues/120736 is implemented. # See also https://github.com/flutter/flutter/issues/114373 diff --git a/.cirrus.yml b/.cirrus.yml index eae90c68ef4d..70d9c1100de5 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -116,10 +116,6 @@ task: CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[d6583b08f79f91ea4844c77460f04539965e46ad2fd97fb7c062b4dfe88016228b86ebe8c220ab4187e0c4bd773dc1e7] GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[1a2eebf9367197bbe812d9a0ea83a53a05aeba4bb5e4964fe6a69727883cd87e51238d39237b1f80b0894c48419ac268] - native_unit_test_script: - # Native integration tests are handled by Firebase Test Lab below, so - # only run unit tests. - - ./script/tool_runner.sh native-test --android --no-integration --exclude script/configs/exclude_native_unit_android.yaml firebase_test_lab_script: - if [[ -n "$GCLOUD_FIREBASE_TESTLAB_KEY" ]]; then - echo $GCLOUD_FIREBASE_TESTLAB_KEY > ${HOME}/gcloud-service-key.json diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 6c6746414ef9..a0a0fd8ae6b7 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.8+4 + +* Adjusts SDK checks for better testability. + ## 0.10.8+3 * Fixes unawaited_futures violations. 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 808051583f61..09fbd4408bea 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 @@ -24,8 +24,6 @@ import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; -import android.os.Build; -import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.HandlerThread; @@ -259,7 +257,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { // TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null // once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668 EncoderProfiles recordingProfile = getRecordingProfile(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) { + if (SdkCapabilityChecker.supportsEncoderProfiles() && recordingProfile != null) { mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath); } else { mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath); @@ -469,7 +467,7 @@ public void onClosed(@NonNull CameraCaptureSession session) { }; // Start the session. - if (VERSION.SDK_INT >= VERSION_CODES.P) { + if (SdkCapabilityChecker.supportsSessionConfiguration()) { // Collect all surfaces to render to. List configs = new ArrayList<>(); configs.add(new OutputConfiguration(flutterSurface)); @@ -821,7 +819,7 @@ public void pauseVideoRecording(@NonNull final Result result) { } try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (SdkCapabilityChecker.supportsVideoPause()) { mediaRecorder.pause(); } else { result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); @@ -842,7 +840,7 @@ public void resumeVideoRecording(@NonNull final Result result) { } try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (SdkCapabilityChecker.supportsVideoPause()) { mediaRecorder.resume(); } else { result.error( @@ -1298,8 +1296,8 @@ public void setDescriptionWhileRecording( return; } - // See VideoRenderer.java requires API 26 to switch camera while recording - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) { + // See VideoRenderer.java; support for this EGL extension is required to switch camera while recording. + if (!SdkCapabilityChecker.supportsEglRecordableAndroid()) { result.error( "setDescriptionWhileRecordingFailed", "Device does not support switching the camera while recording", diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java index 805f18298958..4f5e43593e0d 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java @@ -11,6 +11,7 @@ import android.hardware.camera2.TotalCaptureResult; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; @@ -25,6 +26,13 @@ class CameraCaptureCallback extends CaptureCallback { private final CaptureTimeoutsWrapper captureTimeouts; private final CameraCaptureProperties captureProps; + // Lookup keys for state; overrideable for unit tests since Mockito can't mock them. + @VisibleForTesting @NonNull + CaptureResult.Key aeStateKey = CaptureResult.CONTROL_AE_STATE; + + @VisibleForTesting @NonNull + CaptureResult.Key afStateKey = CaptureResult.CONTROL_AE_STATE; + private CameraCaptureCallback( @NonNull CameraCaptureStateListener cameraStateListener, @NonNull CaptureTimeoutsWrapper captureTimeouts, @@ -69,8 +77,8 @@ public void setCameraState(@NonNull CameraState state) { } private void process(CaptureResult result) { - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer aeState = result.get(aeStateKey); + Integer afState = result.get(afStateKey); // Update capture properties if (result instanceof TotalCaptureResult) { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java index 55ca63caed4e..df9b06b85515 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java @@ -32,7 +32,7 @@ public final class CameraRegionUtils { @NonNull public static Size getCameraBoundaries( @NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + if (SdkCapabilityChecker.supportsDistortionCorrection() && supportsDistortionCorrection(cameraProperties)) { // Get the current distortion correction mode. Integer distortionCorrectionMode = diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DeviceInfo.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DeviceInfo.java new file mode 100644 index 000000000000..fb1e92a8a645 --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DeviceInfo.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.camera; + +import android.os.Build; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +/** Wraps BUILD device info, allowing for overriding it in unit tests. */ +public class DeviceInfo { + @VisibleForTesting public static @Nullable String BRAND = Build.BRAND; + + @VisibleForTesting public static @Nullable String MODEL = Build.MODEL; + + public static @Nullable String getBrand() { + return BRAND; + } + + public static @Nullable String getModel() { + return MODEL; + } +} 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 new file mode 100644 index 000000000000..1ff512672d99 --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/SdkCapabilityChecker.java @@ -0,0 +1,60 @@ +// 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.camera; + +import android.annotation.SuppressLint; +import android.os.Build; +import androidx.annotation.ChecksSdkIntAtLeast; +import androidx.annotation.VisibleForTesting; + +/** Abstracts SDK version checks, and allows overriding them in unit tests. */ +public class SdkCapabilityChecker { + /** The current SDK version, overridable for testing. */ + @SuppressLint("AnnotateVersionCheck") + @VisibleForTesting + public static int SDK_VERSION = Build.VERSION.SDK_INT; + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) + public static boolean supportsDistortionCorrection() { + // See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES + return SDK_VERSION >= Build.VERSION_CODES.P; + } + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) + public static boolean supportsEglRecordableAndroid() { + // See https://developer.android.com/reference/android/opengl/EGLExt#EGL_RECORDABLE_ANDROID + return SDK_VERSION >= Build.VERSION_CODES.O; + } + + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) + public static boolean supportsEncoderProfiles() { + // See https://developer.android.com/reference/android/media/EncoderProfiles + 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 + return SDK_VERSION >= Build.VERSION_CODES.R; + } +} diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java index baa4157688e1..35e8dd32b09e 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java @@ -167,7 +167,7 @@ void configureOpenGL() { "cannot configure OpenGL. missing EGL_ANDROID_presentation_time"); int[] attribList; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + if (SdkCapabilityChecker.supportsEglRecordableAndroid()) { attribList = new int[] { EGL14.EGL_RED_SIZE, 8, diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java index 1f3104bbf4cf..408e7a16b564 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java @@ -6,11 +6,11 @@ import android.annotation.SuppressLint; import android.hardware.camera2.CaptureRequest; -import android.os.Build; import android.util.Range; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.DeviceInfo; import io.flutter.plugins.camera.features.CameraFeature; /** @@ -55,7 +55,9 @@ public FpsRangeFeature(@NonNull CameraProperties cameraProperties) { } private boolean isPixel4A() { - return Build.BRAND.equals("google") && Build.MODEL.equals("Pixel 4a"); + String brand = DeviceInfo.getBrand(); + String model = DeviceInfo.getModel(); + return brand != null && brand.equals("google") && model != null && model.equals("Pixel 4a"); } @NonNull 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 d98328dcce4c..ae7c2262ea7f 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 @@ -6,12 +6,11 @@ import android.annotation.SuppressLint; import android.hardware.camera2.CaptureRequest; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.util.Log; 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; @@ -36,7 +35,7 @@ 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 (VERSION.SDK_INT >= VERSION_CODES.M) { + if (SdkCapabilityChecker.supportsMarshmallowNoiseReductionModes()) { NOISE_REDUCTION_MODES.put( NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL); NOISE_REDUCTION_MODES.put( diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java index 4cba44816639..57ceddb39630 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java @@ -15,6 +15,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.SdkCapabilityChecker; import io.flutter.plugins.camera.features.CameraFeature; import java.util.List; @@ -126,7 +127,7 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) if (preset.ordinal() > ResolutionPreset.high.ordinal()) { preset = ResolutionPreset.high; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SdkCapabilityChecker.supportsEncoderProfiles()) { EncoderProfiles profile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset); List videoProfiles = profile.getVideoProfiles(); @@ -268,7 +269,7 @@ private void configureResolution(ResolutionPreset resolutionPreset, int cameraId } boolean captureSizeCalculated = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SdkCapabilityChecker.supportsEncoderProfiles()) { recordingProfileLegacy = null; recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java index dda06845da2f..4110ef3f05cd 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java @@ -7,9 +7,9 @@ import android.annotation.SuppressLint; import android.graphics.Rect; import android.hardware.camera2.CaptureRequest; -import android.os.Build; import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; +import io.flutter.plugins.camera.SdkCapabilityChecker; import io.flutter.plugins.camera.features.CameraFeature; /** Controls the zoom configuration on the {@link android.hardware.camera2} API. */ @@ -37,7 +37,7 @@ public ZoomLevelFeature(@NonNull CameraProperties cameraProperties) { return; } // On Android 11+ CONTROL_ZOOM_RATIO_RANGE should be use to get the zoom ratio directly as minimum zoom does not have to be 1.0f. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (SdkCapabilityChecker.supportsZoomRatio()) { minimumZoomLevel = cameraProperties.getScalerMinZoomRatio(); maximumZoomLevel = cameraProperties.getScalerMaxZoomRatio(); } else { @@ -83,7 +83,7 @@ public void updateBuilder(@NonNull CaptureRequest.Builder requestBuilder) { // On Android 11+ CONTROL_ZOOM_RATIO can be set to a zoom ratio and the camera feed will compute // how to zoom on its own accounting for multiple logical cameras. // Prior the image cropping window must be calculated and set manually. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (SdkCapabilityChecker.supportsZoomRatio()) { requestBuilder.set( CaptureRequest.CONTROL_ZOOM_RATIO, ZoomUtils.computeZoomRatio(currentSetting, minimumZoomLevel, maximumZoomLevel)); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index 966019bb1431..d41c3335f221 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -7,8 +7,8 @@ import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.media.MediaRecorder; -import android.os.Build; import androidx.annotation.NonNull; +import io.flutter.plugins.camera.SdkCapabilityChecker; import java.io.IOException; public class MediaRecorderBuilder { @@ -78,7 +78,7 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) { + if (SdkCapabilityChecker.supportsEncoderProfiles() && encoderProfiles != null) { EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0); EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java index 934aff857ec7..56f5af93aa18 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java @@ -20,7 +20,6 @@ import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; import io.flutter.plugins.camera.types.Timeout; -import io.flutter.plugins.camera.utils.TestUtils; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; @@ -89,17 +88,13 @@ protected void setUp() throws Exception { when(mockCaptureTimeouts.getPreCaptureFocusing()).thenReturn(mockTimeout); when(mockCaptureTimeouts.getPreCaptureMetering()).thenReturn(mockTimeout); - Key mockAeStateKey = mock(Key.class); - Key mockAfStateKey = mock(Key.class); - - TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AE_STATE", mockAeStateKey); - TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AF_STATE", mockAfStateKey); - mockedStaticTimeout.when(() -> Timeout.create(1000)).thenReturn(mockTimeout); cameraCaptureCallback = CameraCaptureCallback.create( mockCaptureStateListener, mockCaptureTimeouts, mockCaptureProps); + cameraCaptureCallback.aeStateKey = mock(Key.class); + cameraCaptureCallback.afStateKey = mock(Key.class); } @Override @@ -107,17 +102,14 @@ protected void tearDown() throws Exception { super.tearDown(); mockedStaticTimeout.close(); - - TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AE_STATE", null); - TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AF_STATE", null); } @Override protected void runTest() throws Throwable { - when(mockPartialCaptureResult.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(afState); - when(mockPartialCaptureResult.get(CaptureResult.CONTROL_AE_STATE)).thenReturn(aeState); - when(mockTotalCaptureResult.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(afState); - when(mockTotalCaptureResult.get(CaptureResult.CONTROL_AE_STATE)).thenReturn(aeState); + when(mockPartialCaptureResult.get(cameraCaptureCallback.afStateKey)).thenReturn(afState); + when(mockPartialCaptureResult.get(cameraCaptureCallback.aeStateKey)).thenReturn(aeState); + when(mockTotalCaptureResult.get(cameraCaptureCallback.afStateKey)).thenReturn(afState); + when(mockTotalCaptureResult.get(cameraCaptureCallback.aeStateKey)).thenReturn(aeState); cameraCaptureCallback.setCameraState(cameraState); if (isTimedOut) { diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java index 4c0164981b74..b88765d2f2fd 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java @@ -15,7 +15,6 @@ import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.util.Size; -import io.flutter.plugins.camera.utils.TestUtils; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; @@ -242,6 +241,6 @@ public void getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenRunningP } private static void updateSdkVersion(int version) { - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", version); + SdkCapabilityChecker.SDK_VERSION = version; } } 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 c7b5b768783f..db05691ada49 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 @@ -150,7 +150,7 @@ public void before() { @After public void after() { - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 0); + SdkCapabilityChecker.SDK_VERSION = 0; mockHandlerThreadFactory.close(); mockHandlerFactory.close(); } @@ -540,7 +540,7 @@ public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); + SdkCapabilityChecker.SDK_VERSION = 24; camera.pauseVideoRecording(mockResult); @@ -552,7 +552,7 @@ public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { @Test public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() { TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23); + SdkCapabilityChecker.SDK_VERSION = 23; MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.pauseVideoRecording(mockResult); @@ -568,7 +568,7 @@ public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCo MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); + SdkCapabilityChecker.SDK_VERSION = 24; IllegalStateException expectedException = new IllegalStateException("Test error message"); @@ -599,7 +599,7 @@ public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); + SdkCapabilityChecker.SDK_VERSION = 24; camera.resumeVideoRecording(mockResult); @@ -609,27 +609,40 @@ public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { } @Test - public void setDescriptionWhileRecording() { + public void setDescriptionWhileRecording_errorsWhenUnsupported() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); + SdkCapabilityChecker.SDK_VERSION = Build.VERSION_CODES.LOLLIPOP; final CameraProperties newCameraProperties = mock(CameraProperties.class); camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) { - verify(mockResult, times(1)) - .error( - eq("setDescriptionWhileRecordingFailed"), - eq("Device does not support switching the camera while recording"), - eq(null)); - } else { - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); - } + verify(mockResult, times(1)) + .error( + eq("setDescriptionWhileRecordingFailed"), + eq("Device does not support switching the camera while recording"), + eq(null)); + } + + @Test + public void setDescriptionWhileRecording_succeedsWhenSupported() { + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); + VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); + TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); + TestUtils.setPrivateField(camera, "recordingVideo", true); + TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); + SdkCapabilityChecker.SDK_VERSION = Build.VERSION_CODES.O; + + final CameraProperties newCameraProperties = mock(CameraProperties.class); + camera.setDescriptionWhileRecording(mockResult, newCameraProperties); + + verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any(), any(), any()); } @Test @@ -767,7 +780,7 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() { TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23); + SdkCapabilityChecker.SDK_VERSION = 23; MethodChannel.Result mockResult = mock(MethodChannel.Result.class); @@ -784,7 +797,7 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); + SdkCapabilityChecker.SDK_VERSION = 24; IllegalStateException expectedException = new IllegalStateException("Test error message"); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java index 93cfe5523df3..a7d981c69a28 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java @@ -7,10 +7,9 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import android.os.Build; import android.util.Range; import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.utils.TestUtils; +import io.flutter.plugins.camera.DeviceInfo; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -19,8 +18,8 @@ public class FpsRangeFeaturePixel4aTest { @Test public void ctor_shouldInitializeFpsRangeWith30WhenDeviceIsPixel4a() { - TestUtils.setFinalStatic(Build.class, "BRAND", "google"); - TestUtils.setFinalStatic(Build.class, "MODEL", "Pixel 4a"); + DeviceInfo.BRAND = "google"; + DeviceInfo.MODEL = "Pixel 4a"; FpsRangeFeature fpsRangeFeature = new FpsRangeFeature(mock(CameraProperties.class)); Range range = fpsRangeFeature.getValue(); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java index ea67636e2ed1..4dc68af94ca3 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java @@ -13,10 +13,9 @@ import static org.mockito.Mockito.when; import android.hardware.camera2.CaptureRequest; -import android.os.Build; import android.util.Range; import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.utils.TestUtils; +import io.flutter.plugins.camera.DeviceInfo; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -24,14 +23,14 @@ public class FpsRangeFeatureTest { @Before public void before() { - TestUtils.setFinalStatic(Build.class, "BRAND", "Test Brand"); - TestUtils.setFinalStatic(Build.class, "MODEL", "Test Model"); + DeviceInfo.BRAND = "Test Brand"; + DeviceInfo.MODEL = "Test Model"; } @After public void after() { - TestUtils.setFinalStatic(Build.class, "BRAND", null); - TestUtils.setFinalStatic(Build.class, "MODEL", null); + DeviceInfo.BRAND = null; + DeviceInfo.MODEL = null; } @Test 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 b89aad0f6773..93f81c6c1539 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 @@ -15,9 +15,8 @@ import static org.mockito.Mockito.when; import android.hardware.camera2.CaptureRequest; -import android.os.Build.VERSION; import io.flutter.plugins.camera.CameraProperties; -import io.flutter.plugins.camera.utils.TestUtils; +import io.flutter.plugins.camera.SdkCapabilityChecker; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -25,15 +24,15 @@ public class NoiseReductionFeatureTest { @Before public void before() { - // Make sure the VERSION.SDK_INT field returns 23, to allow using all available + // Make sure the SDK_VERSION field returns 23, to allow using all available // noise reduction modes in tests. - TestUtils.setFinalStatic(VERSION.class, "SDK_INT", 23); + SdkCapabilityChecker.SDK_VERSION = 23; } @After public void after() { - // Make sure we reset the VERSION.SDK_INT field to it's original value. - TestUtils.setFinalStatic(VERSION.class, "SDK_INT", 0); + // Make sure we reset the SDK_VERSION field to it's original value. + SdkCapabilityChecker.SDK_VERSION = 0; } @Test diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java index 4d5826967009..344f376ffadc 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java @@ -20,8 +20,7 @@ import android.hardware.camera2.CaptureRequest; import android.os.Build; import io.flutter.plugins.camera.CameraProperties; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import io.flutter.plugins.camera.SdkCapabilityChecker; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -118,7 +117,7 @@ public void getDebugName_shouldReturnTheNameOfTheFeature() { public void getValue_shouldReturnNullIfNotSet() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); - assertEquals(1.0, (float) zoomLevelFeature.getValue(), 0); + assertEquals(1.0, zoomLevelFeature.getValue(), 0); } @Test @@ -127,7 +126,7 @@ public void getValue_shouldEchoSetValue() { zoomLevelFeature.setValue(2.3f); - assertEquals(2.3f, (float) zoomLevelFeature.getValue(), 0); + assertEquals(2.3f, zoomLevelFeature.getValue(), 0); } @Test @@ -209,11 +208,6 @@ public void checkZoomLevelFeature_callsScalarMaxZoomRatioOnAndroidR() throws Exc } static void setSdkVersion(int sdkVersion) throws Exception { - Field sdkInt = Build.VERSION.class.getField("SDK_INT"); - sdkInt.setAccessible(true); - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(sdkInt, sdkInt.getModifiers() & ~Modifier.FINAL); - sdkInt.set(null, sdkVersion); + SdkCapabilityChecker.SDK_VERSION = sdkVersion; } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java index fce99b54384b..79911a0b0b57 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java @@ -5,25 +5,9 @@ package io.flutter.plugins.camera.utils; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import org.junit.Assert; public class TestUtils { - public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { - try { - Field field = classToModify.getField(fieldName); - field.setAccessible(true); - - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(null, newValue); - } catch (Exception e) { - Assert.fail("Unable to mock static field: " + fieldName); - } - } - public static void setPrivateField(T instance, String fieldName, Object newValue) { try { Field field = instance.getClass().getDeclaredField(fieldName); diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 4a2703ce0777..307506a42f90 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,7 +3,7 @@ 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.8+3 +version: 0.10.8+4 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 4499364cd4e8..2da6f23dc8ae 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0+2 + +* Adjusts SDK checks for better testability. + ## 0.5.0+1 * Bumps androidx.annotation:annotation from 1.5.0 to 1.6.0. diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java index 523ea30f68ba..32502ed2693a 100644 --- a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java +++ b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java @@ -15,6 +15,7 @@ import android.provider.OpenableColumns; import android.util.Log; import android.webkit.MimeTypeMap; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -38,7 +39,8 @@ public class FileSelectorApiImpl implements GeneratedFileSelectorApi.FileSelecto // Request code for selecting a directory. private static final int OPEN_DIR = 223; - private final NativeObjectFactory objectFactory; + private final @NonNull NativeObjectFactory objectFactory; + private final @NonNull AndroidSdkChecker sdkChecker; @Nullable ActivityPluginBinding activityPluginBinding; private abstract static class OnResultListener { @@ -60,16 +62,28 @@ DataInputStream newDataInputStream(InputStream inputStream) { } } + // Interface for an injectable SDK version checker. + @VisibleForTesting + interface AndroidSdkChecker { + @ChecksSdkIntAtLeast(parameter = 0) + boolean sdkIsAtLeast(int version); + } + public FileSelectorApiImpl(@NonNull ActivityPluginBinding activityPluginBinding) { - this(activityPluginBinding, new NativeObjectFactory()); + this( + activityPluginBinding, + new NativeObjectFactory(), + (int version) -> Build.VERSION.SDK_INT >= version); } @VisibleForTesting FileSelectorApiImpl( @NonNull ActivityPluginBinding activityPluginBinding, - @NonNull NativeObjectFactory objectFactory) { + @NonNull NativeObjectFactory objectFactory, + @NonNull AndroidSdkChecker sdkChecker) { this.activityPluginBinding = activityPluginBinding; this.objectFactory = objectFactory; + this.sdkChecker = sdkChecker; } @Override @@ -171,9 +185,11 @@ public void onResult(int resultCode, @Nullable Intent data) { @Override public void getDirectoryPath( @Nullable String initialDirectory, @NonNull GeneratedFileSelectorApi.Result result) { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - throw new UnsupportedOperationException( - "Selecting a directory is only supported on versions >= 21"); + if (!sdkChecker.sdkIsAtLeast(android.os.Build.VERSION_CODES.LOLLIPOP)) { + result.error( + new UnsupportedOperationException( + "Selecting a directory is only supported on versions >= 21")); + return; } final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE); diff --git a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java index 5253fc578f88..63dad80954a2 100644 --- a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java +++ b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java @@ -24,11 +24,8 @@ import java.io.DataInputStream; import java.io.FileNotFoundException; import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; -import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -69,23 +66,6 @@ private void mockContentResolver( when(mockResolver.openInputStream(uri)).thenReturn(mock(InputStream.class)); } - @SuppressWarnings("JavaReflectionMemberAccess") - private static void setFinalStatic( - Class classToModify, String fieldName, Object newValue) { - try { - Field field = classToModify.getField(fieldName); - field.setAccessible(true); - - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(null, newValue); - } catch (Exception e) { - Assert.fail("Unable to mock static field: " + fieldName); - } - } - @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void openFileReturnsSuccessfully() throws FileNotFoundException { @@ -100,7 +80,8 @@ public void openFileReturnsSuccessfully() throws FileNotFoundException { when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl(mockActivityBinding, mockObjectFactory); + new FileSelectorApiImpl( + mockActivityBinding, mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); fileSelectorApi.openFile( @@ -152,7 +133,8 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { when(mockActivity.getContentResolver()).thenReturn(mockContentResolver); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl(mockActivityBinding, mockObjectFactory); + new FileSelectorApiImpl( + mockActivityBinding, mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); fileSelectorApi.openFiles( @@ -207,15 +189,16 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void getDirectoryPathReturnsSuccessfully() { - setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); - final Uri mockUri = mock(Uri.class); when(mockUri.toString()).thenReturn("some/path/"); when(mockObjectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE)).thenReturn(mockIntent); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); final FileSelectorApiImpl fileSelectorApi = - new FileSelectorApiImpl(mockActivityBinding, mockObjectFactory); + new FileSelectorApiImpl( + mockActivityBinding, + mockObjectFactory, + (version) -> Build.VERSION_CODES.LOLLIPOP >= version); final GeneratedFileSelectorApi.Result mockResult = mock(GeneratedFileSelectorApi.Result.class); fileSelectorApi.getDirectoryPath(null, mockResult); @@ -232,4 +215,20 @@ public void getDirectoryPathReturnsSuccessfully() { verify(mockResult).success("some/path/"); } + + @Test + public void getDirectoryPath_errorsForUnsupportedVersion() { + final FileSelectorApiImpl fileSelectorApi = + new FileSelectorApiImpl( + mockActivityBinding, + mockObjectFactory, + (version) -> Build.VERSION_CODES.KITKAT >= version); + + @SuppressWarnings("unchecked") + final GeneratedFileSelectorApi.Result mockResult = + mock(GeneratedFileSelectorApi.Result.class); + fileSelectorApi.getDirectoryPath(null, mockResult); + + verify(mockResult).error(any()); + } } diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index cf92e2719b91..18ed625ba7bf 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_android description: Android implementation of the file_selector package. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.0+1 +version: 0.5.0+2 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index c1a07dcda768..0207791c36b0 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.7 + +* Adjusts SDK checks for better testability. + ## 1.0.6 * Removes obsolete null checks on non-nullable values. diff --git a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java index 91950f3d7992..b1f78447afa0 100644 --- a/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java +++ b/packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java @@ -9,7 +9,9 @@ import android.content.Intent; import android.content.pm.ShortcutManager; import android.os.Build; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -24,6 +26,23 @@ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewInte private MethodChannel channel; private MethodCallHandlerImpl handler; private Activity activity; + private final @NonNull AndroidSdkChecker sdkChecker; + + // Interface for an injectable SDK version checker. + @VisibleForTesting + interface AndroidSdkChecker { + @ChecksSdkIntAtLeast(parameter = 0) + boolean sdkIsAtLeast(int version); + } + + public QuickActionsPlugin() { + this((int version) -> Build.VERSION.SDK_INT >= version); + } + + @VisibleForTesting + QuickActionsPlugin(@NonNull AndroidSdkChecker capabilityChecker) { + this.sdkChecker = capabilityChecker; + } /** * Plugin registration. @@ -74,7 +93,7 @@ public void onDetachedFromActivityForConfigChanges() { @Override public boolean onNewIntent(@NonNull Intent intent) { // Do nothing for anything lower than API 25 as the functionality isn't supported. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { + if (!sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.N_MR1)) { return false; } // Notify the Dart side if the launch intent has the intent extra relevant to quick actions. diff --git a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java index 911b789190ab..ebee86645f65 100644 --- a/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java +++ b/packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java @@ -16,7 +16,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutManager; -import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; @@ -25,9 +24,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.nio.ByteBuffer; -import org.junit.After; import org.junit.Test; public class QuickActionsTest { @@ -75,9 +72,9 @@ public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod() throws NoSuchFieldException, IllegalAccessException { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); - final QuickActionsPlugin plugin = new QuickActionsPlugin(); + final QuickActionsPlugin plugin = + new QuickActionsPlugin((version) -> SUPPORTED_BUILD >= version); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); - setBuildVersion(SUPPORTED_BUILD); Field handler = plugin.getClass().getDeclaredField("handler"); handler.setAccessible(true); handler.set(plugin, mock(MethodCallHandlerImpl.class)); @@ -102,13 +99,12 @@ public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod() } @Test - public void onNewIntent_buildVersionUnsupported_doesNotInvokeMethod() - throws NoSuchFieldException, IllegalAccessException { + public void onNewIntent_buildVersionUnsupported_doesNotInvokeMethod() { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); - final QuickActionsPlugin plugin = new QuickActionsPlugin(); + final QuickActionsPlugin plugin = + new QuickActionsPlugin((version) -> UNSUPPORTED_BUILD >= version); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); - setBuildVersion(UNSUPPORTED_BUILD); final Intent mockIntent = createMockIntentWithQuickActionExtra(); // Act @@ -120,13 +116,12 @@ public void onNewIntent_buildVersionUnsupported_doesNotInvokeMethod() } @Test - public void onNewIntent_buildVersionSupported_invokesLaunchMethod() - throws NoSuchFieldException, IllegalAccessException { + public void onNewIntent_buildVersionSupported_invokesLaunchMethod() { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); - final QuickActionsPlugin plugin = new QuickActionsPlugin(); + final QuickActionsPlugin plugin = + new QuickActionsPlugin((version) -> SUPPORTED_BUILD >= version); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); - setBuildVersion(SUPPORTED_BUILD); final Intent mockIntent = createMockIntentWithQuickActionExtra(); final Activity mockMainActivity = mock(Activity.class); when(mockMainActivity.getIntent()).thenReturn(mockIntent); @@ -161,19 +156,4 @@ private Intent createMockIntentWithQuickActionExtra() { when(mockIntent.getStringExtra(EXTRA_ACTION)).thenReturn(QuickActionsTest.SHORTCUT_TYPE); return mockIntent; } - - private void setBuildVersion(int buildVersion) - throws NoSuchFieldException, IllegalAccessException { - Field buildSdkField = Build.VERSION.class.getField("SDK_INT"); - buildSdkField.setAccessible(true); - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(buildSdkField, buildSdkField.getModifiers() & ~Modifier.FINAL); - buildSdkField.set(null, buildVersion); - } - - @After - public void tearDown() throws NoSuchFieldException, IllegalAccessException { - setBuildVersion(0); - } } diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index 04884c40e4a5..0ef46b29d46a 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_android description: An implementation for the Android platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.6 +version: 1.0.7 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 9dc13ccbd388..7a02dcf024e6 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.9.1 + +* Adjusts SDK checks for better testability. + ## 3.9.0 * Adds support for `WebResouceError.url`. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java index 6fdaeeaded04..51ecfcb97596 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java @@ -6,6 +6,7 @@ import android.os.Build; import android.webkit.CookieManager; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import io.flutter.plugin.common.BinaryMessenger; @@ -25,6 +26,14 @@ public class CookieManagerHostApiImpl implements CookieManagerHostApi { private final InstanceManager instanceManager; private final CookieManagerProxy proxy; + private final @NonNull AndroidSdkChecker sdkChecker; + + // Interface for an injectable SDK version checker. + @VisibleForTesting + interface AndroidSdkChecker { + @ChecksSdkIntAtLeast(parameter = 0) + boolean sdkIsAtLeast(int version); + } /** Proxy for constructors and static method of `CookieManager`. */ @VisibleForTesting @@ -47,20 +56,25 @@ public CookieManagerHostApiImpl( this(binaryMessenger, instanceManager, new CookieManagerProxy()); } - /** - * Constructs a {@link CookieManagerHostApiImpl}. - * - * @param binaryMessenger used to communicate with Dart over asynchronous messages - * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param proxy proxy for constructors and static methods of `CookieManager` - */ - public CookieManagerHostApiImpl( + @VisibleForTesting + CookieManagerHostApiImpl( @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager, @NonNull CookieManagerProxy proxy) { + this( + binaryMessenger, instanceManager, proxy, (int version) -> Build.VERSION.SDK_INT >= version); + } + + @VisibleForTesting + CookieManagerHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull CookieManagerProxy proxy, + @NonNull AndroidSdkChecker sdkChecker) { this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; this.proxy = proxy; + this.sdkChecker = sdkChecker; } @Override @@ -76,7 +90,7 @@ public void setCookie(@NonNull Long identifier, @NonNull String url, @NonNull St @Override public void removeAllCookies( @NonNull Long identifier, @NonNull GeneratedAndroidWebView.Result result) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.LOLLIPOP)) { getCookieManagerInstance(identifier).removeAllCookies(result::success); } else { result.success(removeCookiesPreL(getCookieManagerInstance(identifier))); @@ -86,7 +100,7 @@ public void removeAllCookies( @Override public void setAcceptThirdPartyCookies( @NonNull Long identifier, @NonNull Long webViewIdentifier, @NonNull Boolean accept) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.LOLLIPOP)) { getCookieManagerInstance(identifier) .setAcceptThirdPartyCookies( Objects.requireNonNull(instanceManager.getInstance(webViewIdentifier)), accept); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index 14c2b3db5d6c..a0aa51d28caf 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -13,6 +13,7 @@ import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -74,6 +75,15 @@ public static class WebViewPlatformView extends WebView implements PlatformView private WebViewClient currentWebViewClient; private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient; + private final @NonNull AndroidSdkChecker sdkChecker; + + // Interface for an injectable SDK version checker. + @VisibleForTesting + interface AndroidSdkChecker { + @ChecksSdkIntAtLeast(parameter = 0) + boolean sdkIsAtLeast(int version); + } + /** * Creates a {@link WebViewPlatformView}. * @@ -83,10 +93,24 @@ public WebViewPlatformView( @NonNull Context context, @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { + this( + context, + binaryMessenger, + instanceManager, + (int version) -> Build.VERSION.SDK_INT >= version); + } + + @VisibleForTesting + WebViewPlatformView( + @NonNull Context context, + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull AndroidSdkChecker sdkChecker) { super(context); currentWebViewClient = new WebViewClient(); currentWebChromeClient = new WebChromeClientHostApiImpl.SecureWebChromeClient(); api = new WebViewFlutterApiImpl(binaryMessenger, instanceManager); + this.sdkChecker = sdkChecker; setWebViewClient(currentWebViewClient); setWebChromeClient(currentWebChromeClient); @@ -108,7 +132,7 @@ public void dispose() {} @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (sdkChecker.sdkIsAtLeast(Build.VERSION_CODES.O)) { final FlutterView flutterView = tryFindFlutterView(); if (flutterView != null) { flutterView.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java index 5c1abd199913..b2b52d843fa6 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java @@ -15,7 +15,6 @@ import android.webkit.WebView; import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugins.webviewflutter.utils.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -75,13 +74,15 @@ public void setCookie() { @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void clearCookies() { - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); - final long instanceIdentifier = 0; instanceManager.addDartCreatedInstance(mockCookieManager, instanceIdentifier); final CookieManagerHostApiImpl hostApi = - new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager); + new CookieManagerHostApiImpl( + mockBinaryMessenger, + instanceManager, + new CookieManagerHostApiImpl.CookieManagerProxy(), + (int version) -> version <= Build.VERSION_CODES.LOLLIPOP); final Boolean[] successResult = new Boolean[1]; hostApi.removeAllCookies( @@ -108,8 +109,6 @@ public void error(@NonNull Throwable error) {} @Test public void setAcceptThirdPartyCookies() { - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); - final WebView mockWebView = mock(WebView.class); final long webViewIdentifier = 4; instanceManager.addDartCreatedInstance(mockWebView, webViewIdentifier); @@ -120,7 +119,11 @@ public void setAcceptThirdPartyCookies() { instanceManager.addDartCreatedInstance(mockCookieManager, instanceIdentifier); final CookieManagerHostApiImpl hostApi = - new CookieManagerHostApiImpl(mockBinaryMessenger, instanceManager); + new CookieManagerHostApiImpl( + mockBinaryMessenger, + instanceManager, + new CookieManagerHostApiImpl.CookieManagerProxy(), + (int version) -> version <= Build.VERSION_CODES.LOLLIPOP); hostApi.setAcceptThirdPartyCookies(instanceIdentifier, webViewIdentifier, accept); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index 008872755771..02b6d1fd7b79 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -26,7 +26,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi; import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView; -import io.flutter.plugins.webviewflutter.utils.TestUtils; import java.util.HashMap; import java.util.Objects; import org.junit.After; @@ -345,13 +344,16 @@ public void flutterApiCreate() { @Test public void setImportantForAutofillForParentFlutterView() { final WebViewPlatformView webView = - new WebViewPlatformView(mockContext, mockBinaryMessenger, testInstanceManager); + new WebViewPlatformView( + mockContext, + mockBinaryMessenger, + testInstanceManager, + (int version) -> version <= Build.VERSION_CODES.O); final WebViewPlatformView webViewSpy = spy(webView); final FlutterView mockFlutterView = mock(FlutterView.class); when(webViewSpy.getParent()).thenReturn(mockFlutterView); - TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.O); webViewSpy.onAttachedToWindow(); verify(mockFlutterView).setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES); diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java deleted file mode 100644 index deebc4175645..000000000000 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java +++ /dev/null @@ -1,26 +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.utils; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import org.junit.Assert; - -public class TestUtils { - public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { - try { - Field field = classToModify.getField(fieldName); - field.setAccessible(true); - - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(null, newValue); - } catch (Exception e) { - Assert.fail("Unable to mock static field: " + fieldName); - } - } -} diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 6755ed363b43..eb79daf79e1d 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: 3.9.0 +version: 3.9.1 environment: sdk: ">=2.18.0 <4.0.0"