Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 97a0524

Browse files
authored
[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
1 parent 9302d87 commit 97a0524

File tree

6 files changed

+132
-14
lines changed

6 files changed

+132
-14
lines changed

packages/camera/camera_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.4
2+
3+
* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case.
4+
15
## 0.10.3
26

37
* Adds back use of Optional type.

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,11 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException {
258258

259259
MediaRecorderBuilder mediaRecorderBuilder;
260260

261-
if (Build.VERSION.SDK_INT >= 31) {
262-
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath);
261+
// TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null
262+
// once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668
263+
EncoderProfiles recordingProfile = getRecordingProfile();
264+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) {
265+
mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath);
263266
} else {
264267
mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath);
265268
}

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,23 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset)
114114
if (preset.ordinal() > ResolutionPreset.high.ordinal()) {
115115
preset = ResolutionPreset.high;
116116
}
117-
if (Build.VERSION.SDK_INT >= 31) {
117+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
118118
EncoderProfiles profile =
119119
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset);
120120
List<EncoderProfiles.VideoProfile> videoProfiles = profile.getVideoProfiles();
121121
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
122122

123-
return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
124-
} else {
125-
@SuppressWarnings("deprecation")
126-
CamcorderProfile profile =
127-
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
128-
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
123+
if (defaultVideoProfile != null) {
124+
return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
125+
}
129126
}
127+
128+
@SuppressWarnings("deprecation")
129+
// TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S.
130+
// This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668.
131+
CamcorderProfile profile =
132+
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset);
133+
return new Size(profile.videoFrameWidth, profile.videoFrameHeight);
130134
}
131135

132136
/**
@@ -234,15 +238,24 @@ private void configureResolution(ResolutionPreset resolutionPreset, int cameraId
234238
if (!checkIsSupported()) {
235239
return;
236240
}
241+
boolean captureSizeCalculated = false;
237242

238-
if (Build.VERSION.SDK_INT >= 31) {
243+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
244+
recordingProfileLegacy = null;
239245
recordingProfile =
240246
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
241247
List<EncoderProfiles.VideoProfile> videoProfiles = recordingProfile.getVideoProfiles();
242248

243249
EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0);
244-
captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
245-
} else {
250+
251+
if (defaultVideoProfile != null) {
252+
captureSizeCalculated = true;
253+
captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight());
254+
}
255+
}
256+
257+
if (!captureSizeCalculated) {
258+
recordingProfile = null;
246259
@SuppressWarnings("deprecation")
247260
CamcorderProfile camcorderProfile =
248261
getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset);

packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO
7575
if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
7676
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
7777

78-
if (Build.VERSION.SDK_INT >= 31) {
78+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) {
7979
EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0);
8080
EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0);
8181

packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@
55
package io.flutter.plugins.camera.features.resolution;
66

77
import static org.junit.Assert.assertEquals;
8+
import static org.junit.Assert.assertNotNull;
9+
import static org.junit.Assert.assertNull;
810
import static org.junit.Assert.assertTrue;
11+
import static org.mockito.Mockito.any;
12+
import static org.mockito.Mockito.anyInt;
913
import static org.mockito.Mockito.mock;
1014
import static org.mockito.Mockito.mockStatic;
1115
import static org.mockito.Mockito.when;
1216

1317
import android.media.CamcorderProfile;
1418
import android.media.EncoderProfiles;
19+
import android.util.Size;
1520
import io.flutter.plugins.camera.CameraProperties;
21+
import java.util.ArrayList;
1622
import java.util.List;
1723
import org.junit.After;
1824
import org.junit.Before;
1925
import org.junit.Test;
2026
import org.junit.runner.RunWith;
2127
import org.mockito.MockedStatic;
28+
import org.mockito.stubbing.Answer;
2229
import org.robolectric.RobolectricTestRunner;
2330
import org.robolectric.annotation.Config;
2431

@@ -329,4 +336,95 @@ public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() {
329336

330337
mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA));
331338
}
339+
340+
@Config(minSdk = 31)
341+
@Test
342+
public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() {
343+
try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
344+
mockStatic(ResolutionFeature.class)) {
345+
mockedResolutionFeature
346+
.when(
347+
() ->
348+
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
349+
anyInt(), any(ResolutionPreset.class)))
350+
.thenAnswer(
351+
(Answer<EncoderProfiles>)
352+
invocation -> {
353+
EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
354+
List<EncoderProfiles.VideoProfile> videoProfiles =
355+
new ArrayList<EncoderProfiles.VideoProfile>() {
356+
{
357+
add(null);
358+
}
359+
};
360+
when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
361+
return mockEncoderProfiles;
362+
});
363+
mockedResolutionFeature
364+
.when(
365+
() ->
366+
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
367+
anyInt(), any(ResolutionPreset.class)))
368+
.thenAnswer(
369+
(Answer<CamcorderProfile>)
370+
invocation -> {
371+
CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
372+
mockCamcorderProfile.videoFrameWidth = 10;
373+
mockCamcorderProfile.videoFrameHeight = 50;
374+
return mockCamcorderProfile;
375+
});
376+
mockedResolutionFeature
377+
.when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max))
378+
.thenCallRealMethod();
379+
380+
Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max);
381+
assertEquals(testPreviewSize.getWidth(), 10);
382+
assertEquals(testPreviewSize.getHeight(), 50);
383+
}
384+
}
385+
386+
@Config(minSdk = 31)
387+
@Test
388+
public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() {
389+
beforeLegacy();
390+
try (MockedStatic<ResolutionFeature> mockedResolutionFeature =
391+
mockStatic(ResolutionFeature.class)) {
392+
mockedResolutionFeature
393+
.when(
394+
() ->
395+
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset(
396+
anyInt(), any(ResolutionPreset.class)))
397+
.thenAnswer(
398+
(Answer<EncoderProfiles>)
399+
invocation -> {
400+
EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class);
401+
List<EncoderProfiles.VideoProfile> videoProfiles =
402+
new ArrayList<EncoderProfiles.VideoProfile>() {
403+
{
404+
add(null);
405+
}
406+
};
407+
when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles);
408+
return mockEncoderProfiles;
409+
});
410+
mockedResolutionFeature
411+
.when(
412+
() ->
413+
ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy(
414+
anyInt(), any(ResolutionPreset.class)))
415+
.thenAnswer(
416+
(Answer<CamcorderProfile>)
417+
invocation -> {
418+
CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class);
419+
return mockCamcorderProfile;
420+
});
421+
422+
CameraProperties mockCameraProperties = mock(CameraProperties.class);
423+
ResolutionFeature resolutionFeature =
424+
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);
425+
426+
assertNotNull(resolutionFeature.getRecordingProfileLegacy());
427+
assertNull(resolutionFeature.getRecordingProfile());
428+
}
429+
}
332430
}

packages/camera/camera_android/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android
22
description: Android implementation of the camera plugin.
33
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.10.3
5+
version: 0.10.4
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)