Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2b7aa9b
Base classes to support Android camera features
mvanbeusekom Apr 8, 2021
f780742
Fixed formatting
mvanbeusekom Apr 8, 2021
76bc5bd
Applied feedback from PR
mvanbeusekom Apr 20, 2021
0bbed99
Added Android FPS range, resolution and sensor orientation features
mvanbeusekom Apr 8, 2021
1ba738d
Use mockito-inline
mvanbeusekom Apr 9, 2021
de4e70f
Merge remote-tracking branch 'upstream/master' into camera-android/fp…
mvanbeusekom Apr 21, 2021
728346a
Fix issue Pixel 4A
mvanbeusekom May 26, 2021
c014fe3
Merge remote-tracking branch 'upstream/master' into camera-android/fp…
mvanbeusekom May 31, 2021
84f5e73
Added API documentation
mvanbeusekom May 31, 2021
f763f77
Processed feedback on PR
mvanbeusekom May 31, 2021
4a7c73a
Fix formatting
mvanbeusekom May 31, 2021
a890919
Fix formatting
mvanbeusekom May 31, 2021
55a6702
Only exclude 60 FPS limit for Pixel 4a
mvanbeusekom Jun 8, 2021
cd53321
Removed redundant empty line
mvanbeusekom Jun 8, 2021
35831d3
Fixed comment
mvanbeusekom Jun 8, 2021
a9f3142
Test Pixel 4a workaround
mvanbeusekom Jun 8, 2021
551800e
Add tests for orientation updates
mvanbeusekom Jun 10, 2021
68cbc56
Fix formatting
mvanbeusekom Jun 10, 2021
1b137c2
Fix formatting
mvanbeusekom Jun 10, 2021
6514a00
Added missing license header
mvanbeusekom Jun 10, 2021
7f0180e
Accept cameraName as String
mvanbeusekom Jun 16, 2021
24af367
Format
mvanbeusekom Jun 16, 2021
8313dd0
Removed obsolete comment
mvanbeusekom Jun 16, 2021
a39c2e1
update method structure in class to a more logical order
mvanbeusekom Jun 16, 2021
3eecfe9
Merge remote-tracking branch 'origin/master' into camera-android/fps_…
mvanbeusekom Jun 22, 2021
7299b1d
Fix formatting
mvanbeusekom Jun 22, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.flutter.plugins.camera.features.fpsrange;

import android.hardware.camera2.CaptureRequest;
import android.os.Build;
import android.util.Range;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.features.CameraFeature;
Expand All @@ -14,6 +15,7 @@
* API.
*/
public class FpsRangeFeature extends CameraFeature<Range<Integer>> {
private static final Range<Integer> MAX_PIXEL4A_RANGE = new Range<>(30, 30);
private Range<Integer> currentSetting;

/**
Expand All @@ -24,27 +26,35 @@ public class FpsRangeFeature extends CameraFeature<Range<Integer>> {
public FpsRangeFeature(CameraProperties cameraProperties) {
super(cameraProperties);

Range<Integer>[] ranges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges();
if (isPixel4A()) {
// HACK: There is a bug in the Pixel 4A where it cannot support 60fps modes
// even though they are reported as supported by
// `getControlAutoExposureAvailableTargetFpsRanges`.
// For max device compatibility we will keep FPS under 60 even if they report they are
// capable of achieving 60 fps. Highest working FPS is 30.
// https://issuetracker.google.com/issues/189237151
currentSetting = MAX_PIXEL4A_RANGE;
} else {
Range<Integer>[] ranges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges();

if (ranges != null) {
for (Range<Integer> range : ranges) {
int upper = range.getUpper();
if (ranges != null) {
for (Range<Integer> range : ranges) {
int upper = range.getUpper();

// There is a bug in the Pixel 4A where it cannot support 60fps modes
// even though they are reported as supported by
// `getControlAutoExposureAvailableTargetFpsRanges`.
// For max device compatibility we will keep FPS under 60 even if they report they are
// capable of achieving 60 fps.
// https://issuetracker.google.com/issues/189237151
if (upper >= 10 && upper < 60) {
if (currentSetting == null || upper > currentSetting.getUpper()) {
currentSetting = range;
if (upper >= 10) {
if (currentSetting == null || upper > currentSetting.getUpper()) {
currentSetting = range;
}
}
}
}
}
}

private boolean isPixel4A() {
return Build.BRAND.equals("google") && Build.MODEL.equals("Pixel 4a");
}

@Override
public String getDebugName() {
return "FpsRangeFeature";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ public class ResolutionFeature extends CameraFeature<ResolutionPreset> {
*
* @param cameraProperties Collection of characteristics for the current camera device.
* @param resolutionPreset Platform agnostic enum containing resolution information.
* @param cameraId Camera identifier of the camera for which to configure the resolution.
* @param cameraName Camera identifier of the camera for which to configure the resolution.
*/
public ResolutionFeature(
CameraProperties cameraProperties, ResolutionPreset resolutionPreset, int cameraId) {
CameraProperties cameraProperties, ResolutionPreset resolutionPreset, String cameraName) {
super(cameraProperties);
this.currentSetting = resolutionPreset;
this.cameraId = cameraId;

try {
this.cameraId = Integer.parseInt(cameraName, 10);
} catch (NumberFormatException e) {
this.cameraId = -1;
return;
}
configureResolution(resolutionPreset, cameraId);
}

Expand All @@ -54,6 +58,10 @@ public ResolutionFeature(
*/
public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset(
int cameraId, ResolutionPreset preset) {
if (cameraId < 0) {
throw new AssertionError(
"getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers.");
}

switch (preset) {
// All of these cases deliberately fall through to get the best available profile.
Expand Down Expand Up @@ -103,10 +111,12 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) {
}

private void configureResolution(ResolutionPreset resolutionPreset, int cameraId) {
if (!checkIsSupported()) {
return;
}
recordingProfile =
getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset);
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);

previewSize = computeBestPreviewSize(cameraId, resolutionPreset);
}

Expand All @@ -129,7 +139,7 @@ public void setValue(ResolutionPreset value) {
// Always supported
@Override
public boolean checkIsSupported() {
return true;
return cameraId >= 0;
}

@Override
Expand Down Expand Up @@ -157,7 +167,7 @@ public Size getPreviewSize() {
}

/**
* Get the optimal capture size based on the configured resolution.
* Gets the optimal capture size based on the configured resolution.
*
* @return The optimal capture size.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import io.flutter.plugins.camera.DartMessenger;

/**
Expand Down Expand Up @@ -153,13 +154,7 @@ private void startSensorListener() {
new OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int angle) {
if (!isSystemAutoRotationLocked()) {
PlatformChannel.DeviceOrientation newOrientation = calculateSensorOrientation(angle);
if (!newOrientation.equals(lastOrientation)) {
lastOrientation = newOrientation;
messenger.sendDeviceOrientationChangeEvent(newOrientation);
}
}
handleSensorOrientationChange(angle);
}
};
if (orientationEventListener.canDetectOrientation()) {
Expand All @@ -175,19 +170,62 @@ private void startUIListener() {
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (isSystemAutoRotationLocked()) {
PlatformChannel.DeviceOrientation orientation = getUIOrientation();
if (!orientation.equals(lastOrientation)) {
lastOrientation = orientation;
messenger.sendDeviceOrientationChangeEvent(orientation);
}
}
handleUIOrientationChange();
}
};
activity.registerReceiver(broadcastReceiver, orientationIntentFilter);
broadcastReceiver.onReceive(activity, null);
}

/**
* Handles orientation changes based on information from the device's sensors.
*
* <p>This method is visible for testing purposes only and should never be used outside this
* class.
*
* @param angle of the current orientation.
*/
@VisibleForTesting
void handleSensorOrientationChange(int angle) {
if (!isAccelerometerRotationLocked()) {
PlatformChannel.DeviceOrientation orientation = calculateSensorOrientation(angle);
lastOrientation = handleOrientationChange(orientation, lastOrientation, messenger);
}
}

/**
* Handles orientation changes based on change events triggered by the OrientationIntentFilter.
*
* <p>This method is visible for testing purposes only and should never be used outside this
* class.
*/
@VisibleForTesting
void handleUIOrientationChange() {
if (isAccelerometerRotationLocked()) {
PlatformChannel.DeviceOrientation orientation = getUIOrientation();
lastOrientation = handleOrientationChange(orientation, lastOrientation, messenger);
}
}

/**
* Handles orientation changes coming from either the device's sensors or the
* OrientationIntentFilter.
*
* <p>This method is visible for testing purposes only and should never be used outside this
* class.
*/
@VisibleForTesting
static DeviceOrientation handleOrientationChange(
DeviceOrientation newOrientation,
DeviceOrientation previousOrientation,
DartMessenger messenger) {
if (!newOrientation.equals(previousOrientation)) {
messenger.sendDeviceOrientationChangeEvent(newOrientation);
}

return newOrientation;
}

private void stopSensorListener() {
if (orientationEventListener == null) {
return;
Expand All @@ -204,7 +242,7 @@ private void stopUIListener() {
broadcastReceiver = null;
}

private boolean isSystemAutoRotationLocked() {
private boolean isAccelerometerRotationLocked() {
return android.provider.Settings.System.getInt(
activity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0)
!= 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.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 org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
public class FpsRangeFeaturePixel4aTest {
@Test
public void ctor_should_initialize_fps_range_with_30_when_device_is_pixel_4a() {
TestUtils.setFinalStatic(Build.class, "BRAND", "google");
TestUtils.setFinalStatic(Build.class, "MODEL", "Pixel 4a");

FpsRangeFeature fpsRangeFeature = new FpsRangeFeature(mock(CameraProperties.class));
Range<Integer> range = fpsRangeFeature.getValue();
assertEquals(30, (int) range.getLower());
assertEquals(30, (int) range.getUpper());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,27 @@
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 org.junit.After;
import org.junit.Before;
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");
}

@After
public void after() {
TestUtils.setFinalStatic(Build.class, "BRAND", null);
TestUtils.setFinalStatic(Build.class, "MODEL", null);
}

@Test
public void ctor_should_initialize_fps_range_with_highest_upper_value_from_range_array() {
FpsRangeFeature fpsRangeFeature = createTestInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import org.mockito.MockedStatic;

public class ResolutionFeatureTest {
private static final int cameraId = 1;
private static final String cameraName = "1";
private CamcorderProfile mockProfileLow;
private MockedStatic<CamcorderProfile> mockedStaticProfile;

Expand Down Expand Up @@ -82,7 +82,7 @@ public void after() {
public void getDebugName_should_return_the_name_of_the_feature() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
ResolutionFeature resolutionFeature =
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraId);
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);

assertEquals("ResolutionFeature", resolutionFeature.getDebugName());
}
Expand All @@ -91,7 +91,7 @@ public void getDebugName_should_return_the_name_of_the_feature() {
public void getValue_should_return_initial_value_when_not_set() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
ResolutionFeature resolutionFeature =
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraId);
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);

assertEquals(ResolutionPreset.max, resolutionFeature.getValue());
}
Expand All @@ -100,7 +100,7 @@ public void getValue_should_return_initial_value_when_not_set() {
public void getValue_should_echo_setValue() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
ResolutionFeature resolutionFeature =
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraId);
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);

resolutionFeature.setValue(ResolutionPreset.high);

Expand All @@ -111,7 +111,7 @@ public void getValue_should_echo_setValue() {
public void checkIsSupport_returns_true() {
CameraProperties mockCameraProperties = mock(CameraProperties.class);
ResolutionFeature resolutionFeature =
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraId);
new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName);

assertTrue(resolutionFeature.checkIsSupported());
}
Expand Down
Loading