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

Commit 93bd245d authored by Mina Granic's avatar Mina Granic
Browse files

Handle different device/activity orientations for camera compat.

The device can be in portrait or landscape orientation, which affects
camera aspect ratio. The app can request fixed orientation portrait
or landscape, which affects what window- and camera aspect ratio it expects.

This change only introduces different types of treatments - the actual
change in behavior will come later.

Flag: com.android.window.flags.camera_compat_for_freeform
Bug: 365078090
Test: atest WmTests:CameraCompatFreeformPolicyTests

Change-Id: I6991a19461eb2a83e09c04315ea28bceaf3a86a7
parent d2ed3a9c
Loading
Loading
Loading
Loading
+30 −8
Original line number Diff line number Diff line
@@ -36,20 +36,36 @@ public class CameraCompatTaskInfo implements Parcelable {
    public static final int CAMERA_COMPAT_FREEFORM_NONE = 0;

    /**
     * The value to use when portrait camera compat treatment should be applied to a windowed task.
     * The value to use when camera compat treatment should be applied to an activity requesting
     * portrait orientation, while a device is in landscape. Applies only to freeform tasks.
     */
    public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT = 1;
    public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE = 1;

    /**
     * The value to use when landscape camera compat treatment should be applied to a windowed task.
     * The value to use when camera compat treatment should be applied to an activity requesting
     * landscape orientation, while a device is in landscape. Applies only to freeform tasks.
     */
    public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE = 2;
    public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE = 2;

    /**
     * The value to use when camera compat treatment should be applied to an activity requesting
     * portrait orientation, while a device is in portrait. Applies only to freeform tasks.
     */
    public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT = 3;

    /**
     * The value to use when camera compat treatment should be applied to an activity requesting
     * landscape orientation, while a device is in portrait. Applies only to freeform tasks.
     */
    public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT = 4;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "CAMERA_COMPAT_FREEFORM_" }, value = {
            CAMERA_COMPAT_FREEFORM_NONE,
            CAMERA_COMPAT_FREEFORM_PORTRAIT,
            CAMERA_COMPAT_FREEFORM_LANDSCAPE,
            CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
            CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE,
            CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT,
            CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT,
    })
    public @interface FreeformCameraCompatMode {}

@@ -143,8 +159,14 @@ public class CameraCompatTaskInfo implements Parcelable {
            @FreeformCameraCompatMode int freeformCameraCompatMode) {
        return switch (freeformCameraCompatMode) {
            case CAMERA_COMPAT_FREEFORM_NONE -> "inactive";
            case CAMERA_COMPAT_FREEFORM_PORTRAIT -> "portrait";
            case CAMERA_COMPAT_FREEFORM_LANDSCAPE -> "landscape";
            case CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE ->
                    "app-portrait-device-landscape";
            case CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE ->
                    "app-landscape-device-landscape";
            case CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT ->
                    "app-portrait-device-portrait";
            case CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT ->
                    "app-landscape-device-portrait";
            default -> throw new AssertionError(
                    "Unexpected camera compat mode: " + freeformCameraCompatMode);
        };
+38 −6
Original line number Diff line number Diff line
@@ -16,20 +16,28 @@

package com.android.server.wm;

import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;

import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.view.DisplayInfo;
import android.view.Surface;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
@@ -172,11 +180,35 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
    }

    private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) {
        return switch (topActivity.getRequestedConfigurationOrientation()) {
            case ORIENTATION_PORTRAIT -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT;
            case ORIENTATION_LANDSCAPE -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE;
            default -> CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
        };
        final int appOrientation = topActivity.getRequestedConfigurationOrientation();
        // It is very important to check the original (actual) display rotation, and not the
        // sandboxed rotation that camera compat treatment sets.
        final DisplayInfo displayInfo = topActivity.mWmService.mDisplayManagerInternal
                .getDisplayInfo(topActivity.getDisplayId());
        // This treatment targets only devices with portrait natural orientation, which most tablets
        // have.
        // TODO(b/365725400): handle landscape natural orientation.
        if (displayInfo.getNaturalHeight() > displayInfo.getNaturalWidth()) {
            if (appOrientation == ORIENTATION_PORTRAIT) {
                if (isDisplayRotationPortrait(displayInfo.rotation)) {
                    return CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
                } else {
                    return CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
                }
            } else if (appOrientation == ORIENTATION_LANDSCAPE) {
                if (isDisplayRotationPortrait(displayInfo.rotation)) {
                    return CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT;
                } else {
                    return CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE;
                }
            }
        }

        return CAMERA_COMPAT_FREEFORM_NONE;
    }

    private static boolean isDisplayRotationPortrait(@Surface.Rotation int displayRotation) {
        return displayRotation == ROTATION_0 || displayRotation == ROTATION_180;
    }

    /**
+65 −11
Original line number Diff line number Diff line
@@ -16,11 +16,17 @@

package com.android.server.wm;

import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -33,10 +39,10 @@ import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -55,6 +61,8 @@ import android.graphics.Rect;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Surface;

import androidx.test.filters.SmallTest;

@@ -134,6 +142,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
                new CameraCompatFreeformPolicy(mDisplayContent, cameraStateMonitor,
                        mActivityRefresher);

        setDisplayRotation(Surface.ROTATION_90);
        mCameraCompatFreeformPolicy.start();
        cameraStateMonitor.startListeningToCameraState();
    }
@@ -162,17 +171,49 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
    }

    @Test
    public void testCameraConnected_activatesCameraCompatMode() throws Exception {
    public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        setDisplayRotation(Surface.ROTATION_0);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertInCameraCompatMode();
        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
        assertActivityRefreshRequested(/* refreshRequested */ false);
    }

    @Test
    public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        setDisplayRotation(Surface.ROTATION_270);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
        assertActivityRefreshRequested(/* refreshRequested */ false);
    }

    @Test
    public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
        configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
        setDisplayRotation(Surface.ROTATION_0);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
        assertActivityRefreshRequested(/* refreshRequested */ false);
    }

    @Test
    public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
        configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
        setDisplayRotation(Surface.ROTATION_270);
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);

        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
        assertActivityRefreshRequested(/* refreshRequested */ false);
    }

    @Test
    public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
        setDisplayRotation(Surface.ROTATION_270);

        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity);
@@ -180,7 +221,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
        callOnActivityConfigurationChanging(mActivity);

        assertInCameraCompatMode();
        assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
        assertActivityRefreshRequested(/* refreshRequested */ true);
    }

@@ -285,16 +326,14 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
        doReturn(true).when(mActivity).inFreeformWindowingMode();
    }

    private void assertInCameraCompatMode() {
        assertNotEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
                mActivity.mAppCompatController.getAppCompatCameraOverrides()
    private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
        assertEquals(mode, mActivity.mAppCompatController.getAppCompatCameraOverrides()
                        .getFreeformCameraCompatMode());
    }

    private void assertNotInCameraCompatMode() {
        assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE,
                mActivity.mAppCompatController.getAppCompatCameraOverrides()
                        .getFreeformCameraCompatMode());
        assertEquals(CAMERA_COMPAT_FREEFORM_NONE, mActivity.mAppCompatController
                .getAppCompatCameraOverrides().getFreeformCameraCompatMode());
    }

    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
@@ -328,4 +367,19 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
        configuration.windowConfiguration.setAppBounds(bounds);
        return configuration;
    }

    private void setDisplayRotation(@Surface.Rotation int displayRotation) {
        doAnswer(invocation -> {
            DisplayInfo displayInfo = new DisplayInfo();
            mDisplayContent.getDisplay().getDisplayInfo(displayInfo);
            displayInfo.rotation = displayRotation;
            // Set height so that the natural orientation (rotation is 0) is portrait. This is the
            // case for most standard phones and tablets.
            // TODO(b/365725400): handle landscape natural orientation.
            displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600;
            displayInfo.logicalWidth =  displayRotation % 180 == 0 ? 600 : 800;
            return displayInfo;
        }).when(mDisplayContent.mWmService.mDisplayManagerInternal)
                .getDisplayInfo(anyInt());
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.server.wm;


import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -71,7 +73,6 @@ import static org.mockito.Mockito.never;

import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.CameraCompatTaskInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -2025,10 +2026,10 @@ public class TaskTests extends WindowTestsBase {
    public void getTaskInfoPropagatesCameraCompatMode() {
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord activity = task.getTopMostActivity();
        activity.mAppCompatController.getAppCompatCameraOverrides()
                .setFreeformCameraCompatMode(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT);
        activity.mAppCompatController.getAppCompatCameraOverrides().setFreeformCameraCompatMode(
                CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);

        assertEquals(CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT,
        assertEquals(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
                task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode);
    }