[camera_android] Default to legacy recording profile when EncoderProfiles unavailable (#7073)
* Make changes, start test
* Bump versions
* Add test
* Formatting
* Add issue
* Fix test
* Address review
diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md
index 0cb9957..4609b40 100644
--- a/packages/camera/camera_android/CHANGELOG.md
+++ b/packages/camera/camera_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.10.4
+
+* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case.
+
## 0.10.3
* Adds back use of Optional type.
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 7c592b9..b02d686 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
@@ -258,8 +258,11 @@
MediaRecorderBuilder mediaRecorderBuilder;
- if (Build.VERSION.SDK_INT >= 31) {
- mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
+ // 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) {
+ mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath);
} else {
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
}
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 afbd7c3..0ec2fbe 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
@@ -114,19 +114,23 @@
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
preset = ResolutionPreset.high;
}
- if (Build.VERSION.SDK_INT >= 31) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
EncoderProfiles profile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
List<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
- return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
- } else {
- @SuppressWarnings("deprecation")
- CamcorderProfile profile =
- getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
- return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ if (defaultVideoProfile != null) {
+ return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
+ }
}
+
+ @SuppressWarnings("deprecation")
+ // TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S.
+ // This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668.
+ CamcorderProfile profile =
+ getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
+ return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
}
/**
@@ -234,15 +238,24 @@
if (!checkIsSupported()) {
return;
}
+ boolean captureSizeCalculated = false;
- if (Build.VERSION.SDK_INT >= 31) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ recordingProfileLegacy = null;
recordingProfile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
List<EncoderProfiles.VideoProfile> videoProfiles = recordingProfile.getVideoProfiles();
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
- captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
- } else {
+
+ if (defaultVideoProfile != null) {
+ captureSizeCalculated = true;
+ captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
+ }
+ }
+
+ if (!captureSizeCalculated) {
+ recordingProfile = null;
@SuppressWarnings("deprecation")
CamcorderProfile camcorderProfile =
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset);
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 0aebfee..1f9f620 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
@@ -75,7 +75,7 @@
if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
- if (Build.VERSION.SDK_INT >= 31) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && 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/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
index 957b57a..dbc352d 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java
@@ -5,20 +5,27 @@
package io.flutter.plugins.camera.features.resolution;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import android.media.CamcorderProfile;
import android.media.EncoderProfiles;
+import android.util.Size;
import io.flutter.plugins.camera.CameraProperties;
+import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
+import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@@ -329,4 +336,95 @@
mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA));
}
+
+ @Config(minSdk = 31)
+ @Test
+ public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() {
+ try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
+ mockStatic(ResolutionFeature.class)) {
+ mockedResolutionFeature
+ .when(
+ () ->
+ ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
+ anyInt(), any(ResolutionPreset.class)))
+ .thenAnswer(
+ (Answer<EncoderProfiles>)
+ invocation -> {
+ EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
+ List<EncoderProfiles.VideoProfile> videoProfiles =
+ new ArrayList<EncoderProfiles.VideoProfile>() {
+ {
+ add(null);
+ }
+ };
+ when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
+ return mockEncoderProfiles;
+ });
+ mockedResolutionFeature
+ .when(
+ () ->
+ ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
+ anyInt(), any(ResolutionPreset.class)))
+ .thenAnswer(
+ (Answer<CamcorderProfile>)
+ invocation -> {
+ CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
+ mockCamcorderProfile.videoFrameWidth = 10;
+ mockCamcorderProfile.videoFrameHeight = 50;
+ return mockCamcorderProfile;
+ });
+ mockedResolutionFeature
+ .when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max))
+ .thenCallRealMethod();
+
+ Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max);
+ assertEquals(testPreviewSize.getWidth(), 10);
+ assertEquals(testPreviewSize.getHeight(), 50);
+ }
+ }
+
+ @Config(minSdk = 31)
+ @Test
+ public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() {
+ beforeLegacy();
+ try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
+ mockStatic(ResolutionFeature.class)) {
+ mockedResolutionFeature
+ .when(
+ () ->
+ ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
+ anyInt(), any(ResolutionPreset.class)))
+ .thenAnswer(
+ (Answer<EncoderProfiles>)
+ invocation -> {
+ EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
+ List<EncoderProfiles.VideoProfile> videoProfiles =
+ new ArrayList<EncoderProfiles.VideoProfile>() {
+ {
+ add(null);
+ }
+ };
+ when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
+ return mockEncoderProfiles;
+ });
+ mockedResolutionFeature
+ .when(
+ () ->
+ ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
+ anyInt(), any(ResolutionPreset.class)))
+ .thenAnswer(
+ (Answer<CamcorderProfile>)
+ invocation -> {
+ CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
+ return mockCamcorderProfile;
+ });
+
+ CameraProperties mockCameraProperties = mock(CameraProperties.class);
+ ResolutionFeature resolutionFeature =
+ new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
+
+ assertNotNull(resolutionFeature.getRecordingProfileLegacy());
+ assertNull(resolutionFeature.getRecordingProfile());
+ }
+ }
}
diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml
index fed2d29..fb33719 100644
--- a/packages/camera/camera_android/pubspec.yaml
+++ b/packages/camera/camera_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: Android implementation of the camera plugin.
repository: https://github.com/flutter/plugins/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.3
+version: 0.10.4
environment:
sdk: ">=2.14.0 <3.0.0"