diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 42b676f0d816d..9d2cff9901e2f 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -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); } diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java new file mode 100644 index 0000000000000..ea746d1f4fd3e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java @@ -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 backFacingMap = new HashMap() {{ + 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 frontFacingMap = new HashMap() {{ + 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); + }); + } +} diff --git a/services/tests/servicestests/src/com/android/server/camera/OWNERS b/services/tests/servicestests/src/com/android/server/camera/OWNERS new file mode 100644 index 0000000000000..f48a95c5b3a39 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/av:/camera/OWNERS