Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.
* Updates README to reflect that only Android API 24+ is supported.

## 0.11.2

Expand Down
8 changes: 1 addition & 7 deletions packages/camera/camera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A Flutter plugin for iOS, Android and Web allowing access to the device cameras.

| | Android | iOS | Web |
|----------------|---------|-----------|------------------------|
| **Support** | SDK 21+ | iOS 12.0+ | [See `camera_web `][1] |
| **Support** | SDK 24+ | iOS 12.0+ | [See `camera_web `][1] |

## Features

Expand Down Expand Up @@ -37,12 +37,6 @@ If editing `Info.plist` as text, add:

### Android

Change the minimum Android sdk version to 21 (or higher) in your `android/app/build.gradle` file.

```groovy
minSdkVersion 21
```

The endorsed [`camera_android_camerax`][2] implementation of the camera plugin built with CameraX has
better support for more devices than `camera_android`, but has some limitations; please see [this list][3]
for more details. If you wish to use the [`camera_android`][4] implementation of the camera plugin
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.10.10+7

* Updates minimum supported SDK version to Flutter 3.35.
* Removes code for supporting API 21-23.

## 0.10.10+6

* Bumps com.android.tools.build:gradle to 8.12.1.
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ buildFeatures {
compileSdk = 36

defaultConfig {
minSdkVersion 21
minSdkVersion 24
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -891,12 +891,7 @@ public void pauseVideoRecording() {
}

try {
if (SdkCapabilityChecker.supportsVideoPause()) {
mediaRecorder.pause();
} else {
throw new Messages.FlutterError(
"videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null);
}
mediaRecorder.pause();
} catch (IllegalStateException e) {
throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null);
}
Expand All @@ -908,12 +903,7 @@ public void resumeVideoRecording() {
}

try {
if (SdkCapabilityChecker.supportsVideoPause()) {
mediaRecorder.resume();
} else {
throw new Messages.FlutterError(
"videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null);
}
mediaRecorder.resume();
} catch (IllegalStateException e) {
throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ private Long instantiateCamera(String cameraName, Messages.PlatformMediaSettings
return flutterSurfaceTexture.id();
}

// We move catching CameraAccessException out of onMethodCall because it causes a crash
// on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to
// to be able to compile with <21 sdks for apps that want the camera and support earlier version.
@SuppressWarnings("ConstantConditions")
private <T> void handleException(Exception exception, Messages.Result<T> result) {
// The code below exactly preserves the format of the native exceptions generated by pre-Pigeon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ public interface CameraProperties {
* @return android.graphics.Rect Area of the image sensor which corresponds to active pixels prior
* to the application of any geometric distortion correction.
*/
@RequiresApi(api = VERSION_CODES.M)
@NonNull
Rect getSensorInfoPreCorrectionActiveArraySize();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ public Size getSensorInfoPixelArraySize() {
return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
}

@RequiresApi(api = VERSION_CODES.M)
@NonNull
@Override
public Rect getSensorInfoPreCorrectionActiveArraySize() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,12 @@ public static boolean supportsEncoderProfiles() {
return SDK_VERSION >= Build.VERSION_CODES.S;
}

@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.M)
public static boolean supportsMarshmallowNoiseReductionModes() {
// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
return SDK_VERSION >= Build.VERSION_CODES.M;
}

@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
public static boolean supportsSessionConfiguration() {
// See https://developer.android.com/reference/android/hardware/camera2/params/SessionConfiguration
return SDK_VERSION >= Build.VERSION_CODES.P;
}

@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
public static boolean supportsVideoPause() {
// See https://developer.android.com/reference/androidx/camera/video/VideoRecordEvent.Pause
return SDK_VERSION >= Build.VERSION_CODES.N;
}

@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
public static boolean supportsZoomRatio() {
// See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_ZOOM_RATIO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import androidx.annotation.NonNull;
import io.flutter.BuildConfig;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.SdkCapabilityChecker;
import io.flutter.plugins.camera.features.CameraFeature;
import java.util.HashMap;

Expand All @@ -35,12 +34,10 @@ public NoiseReductionFeature(@NonNull CameraProperties cameraProperties) {
NOISE_REDUCTION_MODES.put(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST);
NOISE_REDUCTION_MODES.put(
NoiseReductionMode.highQuality, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY);
if (SdkCapabilityChecker.supportsMarshmallowNoiseReductionModes()) {
NOISE_REDUCTION_MODES.put(
NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL);
NOISE_REDUCTION_MODES.put(
NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG);
}
NOISE_REDUCTION_MODES.put(
NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL);
NOISE_REDUCTION_MODES.put(
NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG);
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,32 +669,22 @@ public void pauseVideoRecording_shouldNotThrowWhenNotRecording() {
}

@Test
public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() {
public void pauseVideoRecording_shouldCallPauseWhenRecording() {
MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
camera.mediaRecorder = mockMediaRecorder;
camera.recordingVideo = true;
SdkCapabilityChecker.SDK_VERSION = 24;

camera.pauseVideoRecording();

verify(mockMediaRecorder, times(1)).pause();
}

@Test
public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() {
camera.recordingVideo = true;
SdkCapabilityChecker.SDK_VERSION = 23;

assertThrows(Messages.FlutterError.class, camera::pauseVideoRecording);
}

@Test
public void
pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() {
MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
camera.mediaRecorder = mockMediaRecorder;
camera.recordingVideo = true;
SdkCapabilityChecker.SDK_VERSION = 24;

IllegalStateException expectedException = new IllegalStateException("Test error message");

Expand All @@ -711,11 +701,10 @@ public void resumeVideoRecording_shouldNotThrowWhenNotRecording() {
}

@Test
public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() {
public void resumeVideoRecording_shouldCallPauseWhenRecording() {
MediaRecorder mockMediaRecorder = mock(MediaRecorder.class);
camera.mediaRecorder = mockMediaRecorder;
camera.recordingVideo = true;
SdkCapabilityChecker.SDK_VERSION = 24;

camera.resumeVideoRecording();

Expand Down Expand Up @@ -866,15 +855,6 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() {
() -> camera.setDescriptionWhileRecording(newCameraProperties));
}

@Test
public void
resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() {
camera.recordingVideo = true;
SdkCapabilityChecker.SDK_VERSION = 23;

assertThrows(Messages.FlutterError.class, camera::resumeVideoRecording);
}

@Test
public void
resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,9 @@

import android.hardware.camera2.CaptureRequest;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.SdkCapabilityChecker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class NoiseReductionFeatureTest {
@Before
public void before() {
// Make sure the SDK_VERSION field returns 23, to allow using all available
// noise reduction modes in tests.
SdkCapabilityChecker.SDK_VERSION = 23;
}

@After
public void after() {
// Make sure we reset the SDK_VERSION field to it's original value.
SdkCapabilityChecker.SDK_VERSION = 0;
}

@Test
public void getDebugName_shouldReturnTheNameOfTheFeature() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ void main() {
testWidgets(
'Capture specific video resolutions',
(WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.isEmpty) {
return;
}
Expand Down Expand Up @@ -114,8 +114,8 @@ void main() {
);

testWidgets('Pause and resume video recording', (WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.isEmpty) {
return;
}
Expand Down Expand Up @@ -164,8 +164,8 @@ void main() {
});

testWidgets('Set description while recording', (WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.length < 2) {
return;
}
Expand Down Expand Up @@ -201,8 +201,8 @@ void main() {
});

testWidgets('Set description', (WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.length < 2) {
return;
}
Expand All @@ -216,8 +216,8 @@ void main() {
});

testWidgets('image streaming', (WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.isEmpty) {
return;
}
Expand Down Expand Up @@ -246,8 +246,8 @@ void main() {
});

testWidgets('recording with image stream', (WidgetTester tester) async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
if (cameras.isEmpty) {
return;
}
Expand Down Expand Up @@ -284,8 +284,8 @@ void main() {

group('Camera settings', () {
Future<CameraDescription> getCamera() async {
final List<CameraDescription> cameras =
await CameraPlatform.instance.availableCameras();
final List<CameraDescription> cameras = await CameraPlatform.instance
.availableCameras();
expect(cameras.isNotEmpty, equals(true));

// Prefer back camera, as it allows more customizations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,17 @@ class CameraValue {
exposureMode: exposureMode ?? this.exposureMode,
focusMode: focusMode ?? this.focusMode,
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
lockedCaptureOrientation:
lockedCaptureOrientation == null
? this.lockedCaptureOrientation
: lockedCaptureOrientation.orNull,
recordingOrientation:
recordingOrientation == null
? this.recordingOrientation
: recordingOrientation.orNull,
lockedCaptureOrientation: lockedCaptureOrientation == null
? this.lockedCaptureOrientation
: lockedCaptureOrientation.orNull,
recordingOrientation: recordingOrientation == null
? this.recordingOrientation
: recordingOrientation.orNull,
isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
description: description ?? this.description,
previewPauseOrientation:
previewPauseOrientation == null
? this.previewPauseOrientation
: previewPauseOrientation.orNull,
previewPauseOrientation: previewPauseOrientation == null
? this.previewPauseOrientation
: previewPauseOrientation.orNull,
);
}

Expand Down Expand Up @@ -364,16 +361,12 @@ class CameraController extends ValueNotifier<CameraValue> {
}

/// Pause video recording.
///
/// This feature is only available on iOS and Android sdk 24+.
Future<void> pauseVideoRecording() async {
await CameraPlatform.instance.pauseVideoRecording(_cameraId);
value = value.copyWith(isRecordingPaused: true);
}

/// Resume video recording after pausing.
///
/// This feature is only available on iOS and Android sdk 24+.
Future<void> resumeVideoRecording() async {
await CameraPlatform.instance.resumeVideoRecording(_cameraId);
value = value.copyWith(isRecordingPaused: false);
Expand Down
Loading