Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb799a86 authored by Emilian Peev's avatar Emilian Peev
Browse files

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
parent fed7243e
Loading
Loading
Loading
Loading
+120 −89
Original line number Diff line number Diff line
@@ -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,8 +431,7 @@ public class CameraServiceProxy extends SystemService
        }
    };

    private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
        private boolean isMOrBelow(Context ctx, String packageName) {
    private static boolean isMOrBelow(Context ctx, String packageName) {
        try {
            return ctx.getPackageManager().getPackageInfo(
                    packageName, 0).applicationInfo.targetSdkVersion <= M;
@@ -439,20 +442,20 @@ public class CameraServiceProxy extends SystemService
    }

    /**
         * Gets whether crop-rotate-scale is needed.
     * Estimate the app crop-rotate-scale compensation value.
     */
        private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
                @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing,
    public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
            @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing,
            boolean ignoreResizableAndSdkCheck) {
        if (taskInfo == null) {
                return false;
            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;
            return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
        }

        // In case the activity behavior is not explicitly overridden, enable the
@@ -463,20 +466,16 @@ public class CameraServiceProxy extends SystemService
            Slog.v(TAG,
                    "The activity is N or above and claims to support resizeable-activity. "
                            + "Crop-rotate-scale is disabled.");
                return false;
            return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
        }

            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;
        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 rotation = display.getRotation();
                switch (rotation) {
        int rotationDegree;
        switch (displayRotation) {
            case Surface.ROTATION_0:
                rotationDegree = 0;
                break;
@@ -489,35 +488,51 @@ public class CameraServiceProxy extends SystemService
            case Surface.ROTATION_270:
                rotationDegree = 270;
                break;
                }
            } else {
                Slog.e(TAG, "Failed to query display manager!");
                return false;
            default:
                Log.e(TAG, "Unsupported display rotation: " + displayRotation);
                return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
        }

            // 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);
        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);
        // 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);
        }

+102 −0
Original line number Diff line number Diff line
/*
 * 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);
        });
    }
}
+1 −0
Original line number Diff line number Diff line
include platform/frameworks/av:/camera/OWNERS