CameraServiceProxy: Estimate rotate&crop override value
Try to estimate the necessary rotate&crop compensation for: 1) All device orientations 2) Front cameras 3) Cameras with landscape sensor orientation Additionally check the resizeable flag of the top activity using the 'topActivityInfo.resizeMode'. Move the heuristics outside of the camera proxy stub inner class and declare them static. This should make unit tests of the logic inside easier. Bug: 202567080 Test: Manual using camera applications, Camera CTS, atest FrameworksServicesTests:com.android.server.camera.CameraServiceProxyTest Change-Id: Iba9b1b26958a373c481a1af0601c08012b5a61e7
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.camera;
|
||||
|
||||
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
|
||||
import static android.os.Build.VERSION_CODES.M;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
@@ -39,7 +40,9 @@ import android.hardware.CameraSessionStats;
|
||||
import android.hardware.CameraStreamStats;
|
||||
import android.hardware.ICameraService;
|
||||
import android.hardware.ICameraServiceProxy;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
|
||||
import android.hardware.display.DisplayManager;
|
||||
@@ -346,13 +349,13 @@ public class CameraServiceProxy extends SystemService
|
||||
|
||||
private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
|
||||
|
||||
private final class TaskInfo {
|
||||
private int frontTaskId;
|
||||
private boolean isResizeable;
|
||||
private boolean isFixedOrientationLandscape;
|
||||
private boolean isFixedOrientationPortrait;
|
||||
private int displayId;
|
||||
private int userId;
|
||||
public static final class TaskInfo {
|
||||
public int frontTaskId;
|
||||
public boolean isResizeable;
|
||||
public boolean isFixedOrientationLandscape;
|
||||
public boolean isFixedOrientationPortrait;
|
||||
public int displayId;
|
||||
public int userId;
|
||||
}
|
||||
|
||||
private final class TaskStateHandler extends TaskStackListener {
|
||||
@@ -367,7 +370,8 @@ public class CameraServiceProxy extends SystemService
|
||||
synchronized (mMapLock) {
|
||||
TaskInfo info = new TaskInfo();
|
||||
info.frontTaskId = taskInfo.taskId;
|
||||
info.isResizeable = taskInfo.isResizeable;
|
||||
info.isResizeable =
|
||||
(taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
|
||||
info.displayId = taskInfo.displayId;
|
||||
info.userId = taskInfo.userId;
|
||||
info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
|
||||
@@ -427,97 +431,108 @@ public class CameraServiceProxy extends SystemService
|
||||
}
|
||||
};
|
||||
|
||||
private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
|
||||
private boolean isMOrBelow(Context ctx, String packageName) {
|
||||
try {
|
||||
return ctx.getPackageManager().getPackageInfo(
|
||||
packageName, 0).applicationInfo.targetSdkVersion <= M;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.e(TAG,"Package name not found!");
|
||||
}
|
||||
return false;
|
||||
private static boolean isMOrBelow(Context ctx, String packageName) {
|
||||
try {
|
||||
return ctx.getPackageManager().getPackageInfo(
|
||||
packageName, 0).applicationInfo.targetSdkVersion <= M;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.e(TAG,"Package name not found!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the app crop-rotate-scale compensation value.
|
||||
*/
|
||||
public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
|
||||
@Nullable TaskInfo taskInfo, int displayRotation, int lensFacing,
|
||||
boolean ignoreResizableAndSdkCheck) {
|
||||
if (taskInfo == null) {
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether crop-rotate-scale is needed.
|
||||
*/
|
||||
private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
|
||||
@Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing,
|
||||
boolean ignoreResizableAndSdkCheck) {
|
||||
if (taskInfo == null) {
|
||||
return false;
|
||||
}
|
||||
// External cameras do not need crop-rotate-scale.
|
||||
if (lensFacing != CameraMetadata.LENS_FACING_FRONT
|
||||
&& lensFacing != CameraMetadata.LENS_FACING_BACK) {
|
||||
Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
// External cameras do not need crop-rotate-scale.
|
||||
if (lensFacing != CameraMetadata.LENS_FACING_FRONT
|
||||
&& lensFacing != CameraMetadata.LENS_FACING_BACK) {
|
||||
Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// In case the activity behavior is not explicitly overridden, enable the
|
||||
// crop-rotate-scale workaround if the app targets M (or below) or is not
|
||||
// resizeable.
|
||||
if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) &&
|
||||
taskInfo.isResizeable) {
|
||||
Slog.v(TAG,
|
||||
"The activity is N or above and claims to support resizeable-activity. "
|
||||
+ "Crop-rotate-scale is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DisplayManager displayManager = ctx.getSystemService(DisplayManager.class);
|
||||
int rotationDegree = 0;
|
||||
if (displayManager != null) {
|
||||
Display display = displayManager.getDisplay(taskInfo.displayId);
|
||||
if (display == null) {
|
||||
Slog.e(TAG, "Invalid display id: " + taskInfo.displayId);
|
||||
return false;
|
||||
}
|
||||
|
||||
int rotation = display.getRotation();
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0:
|
||||
rotationDegree = 0;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
rotationDegree = 90;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
rotationDegree = 180;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
rotationDegree = 270;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Slog.e(TAG, "Failed to query display manager!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here we only need to know whether the camera is landscape or portrait. Therefore we
|
||||
// don't need to consider whether it is a front or back camera. The formula works for
|
||||
// both.
|
||||
boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0);
|
||||
// In case the activity behavior is not explicitly overridden, enable the
|
||||
// crop-rotate-scale workaround if the app targets M (or below) or is not
|
||||
// resizeable.
|
||||
if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) &&
|
||||
taskInfo.isResizeable) {
|
||||
Slog.v(TAG,
|
||||
"Display.getRotation()=" + rotationDegree
|
||||
+ " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation
|
||||
+ " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
|
||||
+ " isFixedOrientationLandscape=" +
|
||||
taskInfo.isFixedOrientationLandscape);
|
||||
// We need to do crop-rotate-scale when camera is landscape and activity is portrait or
|
||||
// vice versa.
|
||||
return (taskInfo.isFixedOrientationPortrait && landscapeCamera)
|
||||
|| (taskInfo.isFixedOrientationLandscape && !landscapeCamera);
|
||||
"The activity is N or above and claims to support resizeable-activity. "
|
||||
+ "Crop-rotate-scale is disabled.");
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) {
|
||||
Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled.");
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
int rotationDegree;
|
||||
switch (displayRotation) {
|
||||
case Surface.ROTATION_0:
|
||||
rotationDegree = 0;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
rotationDegree = 90;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
rotationDegree = 180;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
rotationDegree = 270;
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unsupported display rotation: " + displayRotation);
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
Slog.v(TAG,
|
||||
"Display.getRotation()=" + rotationDegree
|
||||
+ " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
|
||||
+ " isFixedOrientationLandscape=" +
|
||||
taskInfo.isFixedOrientationLandscape);
|
||||
// We are trying to estimate the necessary rotation compensation for clients that
|
||||
// don't handle various display orientations.
|
||||
// The logic that is missing on client side is similar to the reference code
|
||||
// in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation"
|
||||
// is already applied in "CameraUtils::getRotationTransform".
|
||||
// Care should be taken to reverse the rotation direction depending on the camera
|
||||
// lens facing.
|
||||
if (rotationDegree == 0) {
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
|
||||
// Switch direction for front facing cameras
|
||||
rotationDegree = 360 - rotationDegree;
|
||||
}
|
||||
|
||||
switch (rotationDegree) {
|
||||
case 90:
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_90;
|
||||
case 270:
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_270;
|
||||
case 180:
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_180;
|
||||
case 0:
|
||||
default:
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
|
||||
@Override
|
||||
public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation,
|
||||
int lensFacing) {
|
||||
public int getRotateAndCropOverride(String packageName, int lensFacing) {
|
||||
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
|
||||
Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
|
||||
" camera service UID!");
|
||||
return false;
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
// TODO: Modify the sensor orientation in camera characteristics along with any 3A
|
||||
@@ -531,10 +546,10 @@ public class CameraServiceProxy extends SystemService
|
||||
if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName,
|
||||
UserHandle.getUserHandleForUid(taskInfo.userId))) {
|
||||
Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!");
|
||||
return true;
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
} else {
|
||||
Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!");
|
||||
return false;
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
}
|
||||
boolean ignoreResizableAndSdkCheck = false;
|
||||
@@ -544,7 +559,23 @@ public class CameraServiceProxy extends SystemService
|
||||
Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!");
|
||||
ignoreResizableAndSdkCheck = true;
|
||||
}
|
||||
return getNeedCropRotateScale(mContext, packageName, taskInfo, sensorOrientation,
|
||||
|
||||
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
|
||||
int displayRotation;
|
||||
if (displayManager != null) {
|
||||
Display display = displayManager.getDisplay(taskInfo.displayId);
|
||||
if (display == null) {
|
||||
Slog.e(TAG, "Invalid display id: " + taskInfo.displayId);
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
displayRotation = display.getRotation();
|
||||
} else {
|
||||
Slog.e(TAG, "Failed to query display manager!");
|
||||
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
|
||||
}
|
||||
|
||||
return getCropRotateScale(mContext, packageName, taskInfo, displayRotation,
|
||||
lensFacing, ignoreResizableAndSdkCheck);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.camera;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class CameraServiceProxyTest {
|
||||
|
||||
@Test
|
||||
public void testGetCropRotateScale() {
|
||||
|
||||
Context ctx = InstrumentationRegistry.getContext();
|
||||
|
||||
// Check resizeability and SDK
|
||||
CameraServiceProxy.TaskInfo taskInfo = new CameraServiceProxy.TaskInfo();
|
||||
taskInfo.isResizeable = true;
|
||||
taskInfo.displayId = Display.DEFAULT_DISPLAY;
|
||||
taskInfo.isFixedOrientationLandscape = false;
|
||||
taskInfo.isFixedOrientationPortrait = true;
|
||||
// Resizeable apps should be ignored
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
Surface.ROTATION_90 , CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/false)).isEqualTo(
|
||||
CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
|
||||
// Resizeable apps will be considered in case the ignore flag is set
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(
|
||||
CameraMetadata.SCALER_ROTATE_AND_CROP_90);
|
||||
taskInfo.isResizeable = false;
|
||||
// Non-resizeable apps should be considered
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/false)).isEqualTo(
|
||||
CameraMetadata.SCALER_ROTATE_AND_CROP_90);
|
||||
// The ignore flag for non-resizeable should have no effect
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(
|
||||
CameraMetadata.SCALER_ROTATE_AND_CROP_90);
|
||||
// Non-fixed orientation should be ignored
|
||||
taskInfo.isFixedOrientationLandscape = false;
|
||||
taskInfo.isFixedOrientationPortrait = false;
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(
|
||||
CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
|
||||
// Check rotation and lens facing combinations
|
||||
HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
|
||||
put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
|
||||
put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
|
||||
put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
|
||||
put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
|
||||
}};
|
||||
taskInfo.isFixedOrientationPortrait = true;
|
||||
backFacingMap.forEach((key, value) -> {
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
key, CameraCharacteristics.LENS_FACING_BACK,
|
||||
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
|
||||
});
|
||||
HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
|
||||
put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
|
||||
put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
|
||||
put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
|
||||
put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
|
||||
}};
|
||||
frontFacingMap.forEach((key, value) -> {
|
||||
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
|
||||
key, CameraCharacteristics.LENS_FACING_FRONT,
|
||||
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include platform/frameworks/av:/camera/OWNERS
|
||||
Reference in New Issue
Block a user