mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 06:37:17 +08:00
[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
This commit is contained in:
12
.ci.yaml
12
.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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 0.10.8+4
|
||||
|
||||
* Adjusts SDK checks for better testability.
|
||||
|
||||
## 0.10.8+3
|
||||
|
||||
* Fixes unawaited_futures violations.
|
||||
|
@ -24,8 +24,6 @@ import android.media.EncoderProfiles;
|
||||
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 @@ class Camera
|
||||
// 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 @@ class Camera
|
||||
};
|
||||
|
||||
// Start the session.
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.P) {
|
||||
if (SdkCapabilityChecker.supportsSessionConfiguration()) {
|
||||
// Collect all surfaces to render to.
|
||||
List<OutputConfiguration> configs = new ArrayList<>();
|
||||
configs.add(new OutputConfiguration(flutterSurface));
|
||||
@ -821,7 +819,7 @@ class Camera
|
||||
}
|
||||
|
||||
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 @@ class Camera
|
||||
}
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (SdkCapabilityChecker.supportsVideoPause()) {
|
||||
mediaRecorder.resume();
|
||||
} else {
|
||||
result.error(
|
||||
@ -1298,8 +1296,8 @@ class Camera
|
||||
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",
|
||||
|
@ -11,6 +11,7 @@ import android.hardware.camera2.CaptureResult;
|
||||
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<Integer> aeStateKey = CaptureResult.CONTROL_AE_STATE;
|
||||
|
||||
@VisibleForTesting @NonNull
|
||||
CaptureResult.Key<Integer> afStateKey = CaptureResult.CONTROL_AE_STATE;
|
||||
|
||||
private CameraCaptureCallback(
|
||||
@NonNull CameraCaptureStateListener cameraStateListener,
|
||||
@NonNull CaptureTimeoutsWrapper captureTimeouts,
|
||||
@ -69,8 +77,8 @@ class CameraCaptureCallback extends CaptureCallback {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -167,7 +167,7 @@ public class VideoRenderer {
|
||||
"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,
|
||||
|
@ -6,11 +6,11 @@ package io.flutter.plugins.camera.features.fpsrange;
|
||||
|
||||
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 class FpsRangeFeature extends CameraFeature<Range<Integer>> {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -6,12 +6,11 @@ package io.flutter.plugins.camera.features.noisereduction;
|
||||
|
||||
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 class NoiseReductionFeature extends CameraFeature<NoiseReductionMode> {
|
||||
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(
|
||||
|
@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
|
||||
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 @@ public class ResolutionFeature extends CameraFeature<ResolutionPreset> {
|
||||
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<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
|
||||
@ -268,7 +269,7 @@ public class ResolutionFeature extends CameraFeature<ResolutionPreset> {
|
||||
}
|
||||
boolean captureSizeCalculated = false;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (SdkCapabilityChecker.supportsEncoderProfiles()) {
|
||||
recordingProfileLegacy = null;
|
||||
recordingProfile =
|
||||
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
|
||||
|
@ -7,9 +7,9 @@ package io.flutter.plugins.camera.features.zoomlevel;
|
||||
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 class ZoomLevelFeature extends CameraFeature<Float> {
|
||||
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 class ZoomLevelFeature extends CameraFeature<Float> {
|
||||
// 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));
|
||||
|
@ -7,8 +7,8 @@ package io.flutter.plugins.camera.media;
|
||||
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 class MediaRecorderBuilder {
|
||||
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);
|
||||
|
||||
|
@ -20,7 +20,6 @@ import io.flutter.plugins.camera.CameraCaptureCallback.CameraCaptureStateListene
|
||||
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 @@ public class CameraCaptureCallbackStatesTest extends TestCase {
|
||||
when(mockCaptureTimeouts.getPreCaptureFocusing()).thenReturn(mockTimeout);
|
||||
when(mockCaptureTimeouts.getPreCaptureMetering()).thenReturn(mockTimeout);
|
||||
|
||||
Key<Integer> mockAeStateKey = mock(Key.class);
|
||||
Key<Integer> 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 @@ public class CameraCaptureCallbackStatesTest extends TestCase {
|
||||
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) {
|
||||
|
@ -15,7 +15,6 @@ import android.graphics.Rect;
|
||||
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 class CameraRegionUtils_getCameraBoundariesTest {
|
||||
}
|
||||
|
||||
private static void updateSdkVersion(int version) {
|
||||
TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", version);
|
||||
SdkCapabilityChecker.SDK_VERSION = version;
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ public class CameraTest {
|
||||
|
||||
@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 class CameraTest {
|
||||
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 class CameraTest {
|
||||
@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 class CameraTest {
|
||||
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 class CameraTest {
|
||||
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 class CameraTest {
|
||||
}
|
||||
|
||||
@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 class CameraTest {
|
||||
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 class CameraTest {
|
||||
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");
|
||||
|
||||
|
@ -7,10 +7,9 @@ package io.flutter.plugins.camera.features.fpsrange;
|
||||
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 @@ import org.robolectric.RobolectricTestRunner;
|
||||
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<Integer> range = fpsRangeFeature.getValue();
|
||||
|
@ -13,10 +13,9 @@ import static org.mockito.Mockito.verify;
|
||||
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 @@ import org.junit.Test;
|
||||
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
|
||||
|
@ -15,9 +15,8 @@ import static org.mockito.Mockito.verify;
|
||||
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 @@ import org.junit.Test;
|
||||
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
|
||||
|
@ -20,8 +20,7 @@ import android.graphics.Rect;
|
||||
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 class ZoomLevelFeatureTest {
|
||||
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 class ZoomLevelFeatureTest {
|
||||
|
||||
zoomLevelFeature.setValue(2.3f);
|
||||
|
||||
assertEquals(2.3f, (float) zoomLevelFeature.getValue(), 0);
|
||||
assertEquals(2.3f, zoomLevelFeature.getValue(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -209,11 +208,6 @@ public class ZoomLevelFeatureTest {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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 <T> void setFinalStatic(Class<T> classToModify, String fieldName, Object newValue) {
|
||||
try {
|
||||
Field field = classToModify.getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
field.set(null, newValue);
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unable to mock static field: " + fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void setPrivateField(T instance, String fieldName, Object newValue) {
|
||||
try {
|
||||
Field field = instance.getClass().getDeclaredField(fieldName);
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -15,6 +15,7 @@ import android.provider.DocumentsContract;
|
||||
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 @@ public class FileSelectorApiImpl implements GeneratedFileSelectorApi.FileSelecto
|
||||
}
|
||||
}
|
||||
|
||||
// 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 class FileSelectorApiImpl implements GeneratedFileSelectorApi.FileSelecto
|
||||
@Override
|
||||
public void getDirectoryPath(
|
||||
@Nullable String initialDirectory, @NonNull GeneratedFileSelectorApi.Result<String> 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);
|
||||
|
@ -24,11 +24,8 @@ import io.flutter.plugin.common.PluginRegistry;
|
||||
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 @@ public class FileSelectorAndroidPluginTest {
|
||||
when(mockResolver.openInputStream(uri)).thenReturn(mock(InputStream.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
private static <T> void setFinalStatic(
|
||||
Class<T> classToModify, String fieldName, Object newValue) {
|
||||
try {
|
||||
Field field = classToModify.getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
field.set(null, newValue);
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unable to mock static field: " + fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Test
|
||||
public void openFileReturnsSuccessfully() throws FileNotFoundException {
|
||||
@ -100,7 +80,8 @@ public class FileSelectorAndroidPluginTest {
|
||||
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 class FileSelectorAndroidPluginTest {
|
||||
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 class FileSelectorAndroidPluginTest {
|
||||
@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 class FileSelectorAndroidPluginTest {
|
||||
|
||||
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<String> mockResult =
|
||||
mock(GeneratedFileSelectorApi.Result.class);
|
||||
fileSelectorApi.getDirectoryPath(null, mockResult);
|
||||
|
||||
verify(mockResult).error(any());
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 1.0.7
|
||||
|
||||
* Adjusts SDK checks for better testability.
|
||||
|
||||
## 1.0.6
|
||||
|
||||
* Removes obsolete null checks on non-nullable values.
|
||||
|
@ -9,7 +9,9 @@ import android.content.Context;
|
||||
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 class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewInte
|
||||
@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.
|
||||
|
@ -16,7 +16,6 @@ import android.app.Activity;
|
||||
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.BinaryMessenger;
|
||||
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 class QuickActionsTest {
|
||||
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 class QuickActionsTest {
|
||||
}
|
||||
|
||||
@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 class QuickActionsTest {
|
||||
}
|
||||
|
||||
@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 @@ public class QuickActionsTest {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 3.9.1
|
||||
|
||||
* Adjusts SDK checks for better testability.
|
||||
|
||||
## 3.9.0
|
||||
|
||||
* Adds support for `WebResouceError.url`.
|
||||
|
@ -6,6 +6,7 @@ package io.flutter.plugins.webviewflutter;
|
||||
|
||||
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 class CookieManagerHostApiImpl implements CookieManagerHostApi {
|
||||
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 class CookieManagerHostApiImpl implements CookieManagerHostApi {
|
||||
@Override
|
||||
public void removeAllCookies(
|
||||
@NonNull Long identifier, @NonNull GeneratedAndroidWebView.Result<Boolean> 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 class CookieManagerHostApiImpl implements CookieManagerHostApi {
|
||||
@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);
|
||||
|
@ -13,6 +13,7 @@ import android.view.ViewParent;
|
||||
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 class WebViewHostApiImpl implements WebViewHostApi {
|
||||
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 class WebViewHostApiImpl implements WebViewHostApi {
|
||||
@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 class WebViewHostApiImpl implements WebViewHostApi {
|
||||
@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);
|
||||
|
@ -15,7 +15,6 @@ import android.webkit.ValueCallback;
|
||||
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 class CookieManagerTest {
|
||||
@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 class CookieManagerTest {
|
||||
|
||||
@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 class CookieManagerTest {
|
||||
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);
|
||||
|
||||
|
@ -26,7 +26,6 @@ import io.flutter.embedding.android.FlutterView;
|
||||
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 class WebViewTest {
|
||||
@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);
|
||||
|
@ -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 <T> void setFinalStatic(Class<T> classToModify, String fieldName, Object newValue) {
|
||||
try {
|
||||
Field field = classToModify.getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
|
||||
field.set(null, newValue);
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unable to mock static field: " + fieldName);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
Reference in New Issue
Block a user