Loading services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +9 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION import android.annotation.NonNull; import android.annotation.Nullable; import android.app.CameraCompatTaskInfo; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.widget.Toast; Loading Loading @@ -250,6 +251,14 @@ class AppCompatCameraPolicy { cameraCompatFreeformPolicyAspectRatio); } @CameraCompatTaskInfo.FreeformCameraCompatMode static int getCameraCompatFreeformMode(@NonNull ActivityRecord activity) { final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); return cameraPolicy != null && cameraPolicy.mCameraCompatFreeformPolicy != null ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatMode(activity) : CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; } /** * Whether we should apply the min aspect ratio per-app override only when an app is connected * to the camera. Loading services/core/java/com/android/server/wm/AppCompatUtils.java +2 −2 Original line number Diff line number Diff line Loading @@ -185,8 +185,8 @@ final class AppCompatUtils { && aspectRatioOverrides.shouldEnableUserAspectRatioSettings(); appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton); appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed()); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController .getAppCompatCameraOverrides().getFreeformCameraCompatMode(); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task)); } Loading services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +58 −37 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_ 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.WINDOW_CONFIG_APP_BOUNDS; import static android.app.WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION; 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; Loading @@ -35,6 +37,7 @@ 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; Loading Loading @@ -64,8 +67,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @NonNull private final CameraStateMonitor mCameraStateMonitor; private boolean mIsCameraCompatTreatmentPending = false; @Nullable private Task mCameraTask; Loading Loading @@ -100,13 +101,27 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return mIsRunning; } // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled. // Refreshing only when configuration changes after applying camera compat treatment. @Override public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending; return isTreatmentEnabledForActivity(activity, /* shouldCheckOrientation= */ true) && haveCameraCompatAttributesChanged(newConfig, lastReportedConfig); } private boolean haveCameraCompatAttributesChanged(@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { // Camera compat treatment changes the following: // - Letterboxes app bounds to camera compat aspect ratio in app's requested orientation, // - Changes display rotation so it matches what the app expects in its chosen orientation, // - Rotate-and-crop camera feed to match that orientation (this changes iff the display // rotation changes, so no need to check). final long diff = newConfig.windowConfiguration.diff(lastReportedConfig.windowConfiguration, /* compareUndefined= */ true); final boolean appBoundsChanged = (diff & WINDOW_CONFIG_APP_BOUNDS) != 0; final boolean displayRotationChanged = (diff & WINDOW_CONFIG_DISPLAY_ROTATION) != 0; return appBoundsChanged || displayRotationChanged; } /** Loading @@ -132,22 +147,26 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(cameraActivity)) { // Do not check orientation outside of the config recompute, as the app's orientation intent // might be obscured by a fullscreen override. Especially for apps which have a camera // functionality which is not the main focus of the app: while most of the app might work // well in fullscreen, often the camera setup still assumes it will run on a portrait device // in its natural orientation and comes out stretched or sideways. // Config recalculation will later check the original orientation to avoid applying // treatment to apps optimized for large screens. if (!isTreatmentEnabledForActivity(cameraActivity, /* shouldCheckOrientation= */ false)) { return; } final int existingCameraCompatMode = cameraActivity.mAppCompatController .getAppCompatCameraOverrides() .getFreeformCameraCompatMode(); final int newCameraCompatMode = getCameraCompatMode(cameraActivity); if (newCameraCompatMode != existingCameraCompatMode) { mIsCameraCompatTreatmentPending = true; mCameraTask = cameraActivity.getTask(); cameraActivity.mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(newCameraCompatMode); forceUpdateActivityAndTask(cameraActivity); } else { mIsCameraCompatTreatmentPending = false; cameraActivity.recomputeConfiguration(); updateCameraCompatMode(cameraActivity); cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true); cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false); } private void updateCameraCompatMode(@NonNull ActivityRecord cameraActivity) { cameraActivity.mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(getCameraCompatMode(cameraActivity)); } @Override Loading @@ -167,7 +186,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } } mCameraTask = null; mIsCameraCompatTreatmentPending = false; return true; } Loading @@ -184,13 +202,12 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa // Camera compat should direct aspect ratio when in camera compat mode, unless an app has a // different camera compat aspect ratio set: this allows per-app camera compat override // aspect ratio to be smaller than the default. return isInCameraCompatMode(activity) && !activity.mAppCompatController return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled(); } private boolean isInCameraCompatMode(@NonNull ActivityRecord activity) { return activity.mAppCompatController.getAppCompatCameraOverrides() .getFreeformCameraCompatMode() != CAMERA_COMPAT_FREEFORM_NONE; boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) { return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE; } float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) { Loading @@ -201,16 +218,11 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; } private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) { cameraActivity.recomputeConfiguration(); cameraActivity.updateReportedConfigurationAndSend(); Task cameraTask = cameraActivity.getTask(); if (cameraTask != null) { cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } @CameraCompatTaskInfo.FreeformCameraCompatMode int getCameraCompatMode(@NonNull ActivityRecord topActivity) { if (!isTreatmentEnabledForActivity(topActivity, /* shouldCheckOrientation= */ true)) { return CAMERA_COMPAT_FREEFORM_NONE; } private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) { 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. Loading Loading @@ -250,15 +262,24 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa * <ul> * <li>Treatment is enabled. * <li>Camera is active for the package. * <li>The app has a fixed orientation. * <li>The app has a fixed orientation if {@code checkOrientation} is true. * <li>The app is in freeform windowing mode. * </ul> * * @param checkOrientation Whether to take apps orientation into account for this check. Only * fixed-orientation apps should be targeted, but this might be * obscured by OEMs via fullscreen override and the app's original * intent inaccessible when the camera opens. Thus, policy would pass * {@code false} here when considering whether to trigger config * recalculation, and later pass {@code true} during recalculation. */ private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { @VisibleForTesting boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity, boolean checkOrientation) { int orientation = activity.getRequestedConfigurationOrientation(); return isCameraCompatForFreeformEnabledForActivity(activity) && mCameraStateMonitor.isCameraRunningForActivity(activity) && orientation != ORIENTATION_UNDEFINED && (!checkOrientation || orientation != ORIENTATION_UNDEFINED) && activity.inFreeformWindowingMode() // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force-letterbox them. Loading @@ -270,7 +291,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(topActivity) if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true) || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } Loading services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java +45 −4 Original line number Diff line number Diff line Loading @@ -18,16 +18,26 @@ package com.android.server.wm; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import android.app.TaskInfo; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Surface; import androidx.annotation.NonNull; import com.android.window.flags.Flags; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -174,9 +184,13 @@ public class AppCompatUtilsTest extends WindowTestsBase { } @Test @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void getTaskInfoPropagatesCameraCompatMode() { runTestScenario((robot) -> { robot.applyOnActivity(AppCompatActivityRobot::createActivityWithComponentInNewTask); robot.dw().allowEnterDesktopMode(/* isAllowed= */ true); robot.applyOnActivity( AppCompatActivityRobot::createActivityWithComponentInNewTaskAndDisplay); robot.setCameraCompatTreatmentEnabledForActivity(/* enabled= */ true); robot.setFreeformCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); robot.checkTaskInfoFreeformCameraCompatMode( Loading Loading @@ -212,6 +226,15 @@ public class AppCompatUtilsTest extends WindowTestsBase { spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy()); } @Override void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { super.onPostDisplayContentCreation(displayContent); mockPortraitDisplay(displayContent); if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) { spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); } } void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) { // We always create at least an opaque activity in a Task. activity().createNewTaskWithBaseActivity(); Loading @@ -235,8 +258,8 @@ public class AppCompatUtilsTest extends WindowTestsBase { } void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) { activity().top().mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(mode); doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy .mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top()); } void checkTopActivityLetterboxReason(@NonNull String expected) { Loading @@ -258,6 +281,24 @@ public class AppCompatUtilsTest extends WindowTestsBase { Assert.assertEquals(mode, getTopTaskInfo().appCompatTaskInfo .cameraCompatTaskInfo.freeformCameraCompatMode); } void setCameraCompatTreatmentEnabledForActivity(boolean enabled) { doReturn(enabled).when(activity().displayContent().mAppCompatCameraPolicy .mCameraCompatFreeformPolicy).isTreatmentEnabledForActivity( eq(activity().top()), anyBoolean()); } private void mockPortraitDisplay(DisplayContent displayContent) { doAnswer(invocation -> { DisplayInfo displayInfo = new DisplayInfo(); displayContent.getDisplay().getDisplayInfo(displayInfo); displayInfo.rotation = Surface.ROTATION_90; // Set height and width so that the natural orientation (when rotation is 0) is // portrait. displayInfo.logicalHeight = 600; displayInfo.logicalWidth = 800; return displayInfo; }).when(displayContent.mWmService.mDisplayManagerInternal).getDisplayInfo(anyInt()); } } } services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +58 −5 Original line number Diff line number Diff line Loading @@ -223,10 +223,13 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { setDisplayRotation(Surface.ROTATION_270); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity); callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, /* lastLetterbox= */ false); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity); // Activity is letterboxed from the previous configuration change. callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, /* lastLetterbox= */ true); assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); assertActivityRefreshRequested(/* refreshRequested */ true); Loading Loading @@ -262,6 +265,48 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { mActivity)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ false); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); oldConfiguration.windowConfiguration.setDisplayRotation(0); newConfiguration.windowConfiguration.setDisplayRotation(90); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); oldConfiguration.windowConfiguration.setDisplayRotation(0); newConfiguration.windowConfiguration.setDisplayRotation(0); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() Loading Loading @@ -306,6 +351,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() { configureActivity(SCREEN_ORIENTATION_FULL_USER); Loading @@ -318,6 +364,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; Loading @@ -331,8 +378,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { /* delta= */ 0.001); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; Loading Loading @@ -411,9 +458,15 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } private void callOnActivityConfigurationChanging(ActivityRecord activity) { callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true, /* lastLetterbox= */false); } private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew, boolean lastLetterbox) { mActivityRefresher.onActivityConfigurationChanging(activity, /* newConfig */ createConfiguration(/*letterbox=*/ true), /* lastReportedConfig */ createConfiguration(/*letterbox=*/ false)); /* newConfig */ createConfiguration(letterboxNew), /* lastReportedConfig */ createConfiguration(lastLetterbox)); } private Configuration createConfiguration(boolean letterbox) { Loading Loading
services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +9 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION import android.annotation.NonNull; import android.annotation.Nullable; import android.app.CameraCompatTaskInfo; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.widget.Toast; Loading Loading @@ -250,6 +251,14 @@ class AppCompatCameraPolicy { cameraCompatFreeformPolicyAspectRatio); } @CameraCompatTaskInfo.FreeformCameraCompatMode static int getCameraCompatFreeformMode(@NonNull ActivityRecord activity) { final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); return cameraPolicy != null && cameraPolicy.mCameraCompatFreeformPolicy != null ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatMode(activity) : CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; } /** * Whether we should apply the min aspect ratio per-app override only when an app is connected * to the camera. Loading
services/core/java/com/android/server/wm/AppCompatUtils.java +2 −2 Original line number Diff line number Diff line Loading @@ -185,8 +185,8 @@ final class AppCompatUtils { && aspectRatioOverrides.shouldEnableUserAspectRatioSettings(); appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton); appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed()); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController .getAppCompatCameraOverrides().getFreeformCameraCompatMode(); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task)); } Loading
services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +58 −37 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_ 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.WINDOW_CONFIG_APP_BOUNDS; import static android.app.WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION; 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; Loading @@ -35,6 +37,7 @@ 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; Loading Loading @@ -64,8 +67,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @NonNull private final CameraStateMonitor mCameraStateMonitor; private boolean mIsCameraCompatTreatmentPending = false; @Nullable private Task mCameraTask; Loading Loading @@ -100,13 +101,27 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return mIsRunning; } // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled. // Refreshing only when configuration changes after applying camera compat treatment. @Override public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { return isTreatmentEnabledForActivity(activity) && mIsCameraCompatTreatmentPending; return isTreatmentEnabledForActivity(activity, /* shouldCheckOrientation= */ true) && haveCameraCompatAttributesChanged(newConfig, lastReportedConfig); } private boolean haveCameraCompatAttributesChanged(@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { // Camera compat treatment changes the following: // - Letterboxes app bounds to camera compat aspect ratio in app's requested orientation, // - Changes display rotation so it matches what the app expects in its chosen orientation, // - Rotate-and-crop camera feed to match that orientation (this changes iff the display // rotation changes, so no need to check). final long diff = newConfig.windowConfiguration.diff(lastReportedConfig.windowConfiguration, /* compareUndefined= */ true); final boolean appBoundsChanged = (diff & WINDOW_CONFIG_APP_BOUNDS) != 0; final boolean displayRotationChanged = (diff & WINDOW_CONFIG_DISPLAY_ROTATION) != 0; return appBoundsChanged || displayRotationChanged; } /** Loading @@ -132,22 +147,26 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @Override public void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(cameraActivity)) { // Do not check orientation outside of the config recompute, as the app's orientation intent // might be obscured by a fullscreen override. Especially for apps which have a camera // functionality which is not the main focus of the app: while most of the app might work // well in fullscreen, often the camera setup still assumes it will run on a portrait device // in its natural orientation and comes out stretched or sideways. // Config recalculation will later check the original orientation to avoid applying // treatment to apps optimized for large screens. if (!isTreatmentEnabledForActivity(cameraActivity, /* shouldCheckOrientation= */ false)) { return; } final int existingCameraCompatMode = cameraActivity.mAppCompatController .getAppCompatCameraOverrides() .getFreeformCameraCompatMode(); final int newCameraCompatMode = getCameraCompatMode(cameraActivity); if (newCameraCompatMode != existingCameraCompatMode) { mIsCameraCompatTreatmentPending = true; mCameraTask = cameraActivity.getTask(); cameraActivity.mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(newCameraCompatMode); forceUpdateActivityAndTask(cameraActivity); } else { mIsCameraCompatTreatmentPending = false; cameraActivity.recomputeConfiguration(); updateCameraCompatMode(cameraActivity); cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true); cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false); } private void updateCameraCompatMode(@NonNull ActivityRecord cameraActivity) { cameraActivity.mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(getCameraCompatMode(cameraActivity)); } @Override Loading @@ -167,7 +186,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } } mCameraTask = null; mIsCameraCompatTreatmentPending = false; return true; } Loading @@ -184,13 +202,12 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa // Camera compat should direct aspect ratio when in camera compat mode, unless an app has a // different camera compat aspect ratio set: this allows per-app camera compat override // aspect ratio to be smaller than the default. return isInCameraCompatMode(activity) && !activity.mAppCompatController return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled(); } private boolean isInCameraCompatMode(@NonNull ActivityRecord activity) { return activity.mAppCompatController.getAppCompatCameraOverrides() .getFreeformCameraCompatMode() != CAMERA_COMPAT_FREEFORM_NONE; boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) { return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE; } float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) { Loading @@ -201,16 +218,11 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; } private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) { cameraActivity.recomputeConfiguration(); cameraActivity.updateReportedConfigurationAndSend(); Task cameraTask = cameraActivity.getTask(); if (cameraTask != null) { cameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } @CameraCompatTaskInfo.FreeformCameraCompatMode int getCameraCompatMode(@NonNull ActivityRecord topActivity) { if (!isTreatmentEnabledForActivity(topActivity, /* shouldCheckOrientation= */ true)) { return CAMERA_COMPAT_FREEFORM_NONE; } private static int getCameraCompatMode(@NonNull ActivityRecord topActivity) { 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. Loading Loading @@ -250,15 +262,24 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa * <ul> * <li>Treatment is enabled. * <li>Camera is active for the package. * <li>The app has a fixed orientation. * <li>The app has a fixed orientation if {@code checkOrientation} is true. * <li>The app is in freeform windowing mode. * </ul> * * @param checkOrientation Whether to take apps orientation into account for this check. Only * fixed-orientation apps should be targeted, but this might be * obscured by OEMs via fullscreen override and the app's original * intent inaccessible when the camera opens. Thus, policy would pass * {@code false} here when considering whether to trigger config * recalculation, and later pass {@code true} during recalculation. */ private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { @VisibleForTesting boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity, boolean checkOrientation) { int orientation = activity.getRequestedConfigurationOrientation(); return isCameraCompatForFreeformEnabledForActivity(activity) && mCameraStateMonitor.isCameraRunningForActivity(activity) && orientation != ORIENTATION_UNDEFINED && (!checkOrientation || orientation != ORIENTATION_UNDEFINED) && activity.inFreeformWindowingMode() // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force-letterbox them. Loading @@ -270,7 +291,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity, @NonNull String cameraId) { if (!isTreatmentEnabledForActivity(topActivity) if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true) || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } Loading
services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java +45 −4 Original line number Diff line number Diff line Loading @@ -18,16 +18,26 @@ package com.android.server.wm; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import android.app.TaskInfo; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Surface; import androidx.annotation.NonNull; import com.android.window.flags.Flags; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -174,9 +184,13 @@ public class AppCompatUtilsTest extends WindowTestsBase { } @Test @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void getTaskInfoPropagatesCameraCompatMode() { runTestScenario((robot) -> { robot.applyOnActivity(AppCompatActivityRobot::createActivityWithComponentInNewTask); robot.dw().allowEnterDesktopMode(/* isAllowed= */ true); robot.applyOnActivity( AppCompatActivityRobot::createActivityWithComponentInNewTaskAndDisplay); robot.setCameraCompatTreatmentEnabledForActivity(/* enabled= */ true); robot.setFreeformCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); robot.checkTaskInfoFreeformCameraCompatMode( Loading Loading @@ -212,6 +226,15 @@ public class AppCompatUtilsTest extends WindowTestsBase { spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy()); } @Override void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { super.onPostDisplayContentCreation(displayContent); mockPortraitDisplay(displayContent); if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) { spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); } } void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) { // We always create at least an opaque activity in a Task. activity().createNewTaskWithBaseActivity(); Loading @@ -235,8 +258,8 @@ public class AppCompatUtilsTest extends WindowTestsBase { } void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) { activity().top().mAppCompatController.getAppCompatCameraOverrides() .setFreeformCameraCompatMode(mode); doReturn(mode).when(activity().top().mDisplayContent.mAppCompatCameraPolicy .mCameraCompatFreeformPolicy).getCameraCompatMode(activity().top()); } void checkTopActivityLetterboxReason(@NonNull String expected) { Loading @@ -258,6 +281,24 @@ public class AppCompatUtilsTest extends WindowTestsBase { Assert.assertEquals(mode, getTopTaskInfo().appCompatTaskInfo .cameraCompatTaskInfo.freeformCameraCompatMode); } void setCameraCompatTreatmentEnabledForActivity(boolean enabled) { doReturn(enabled).when(activity().displayContent().mAppCompatCameraPolicy .mCameraCompatFreeformPolicy).isTreatmentEnabledForActivity( eq(activity().top()), anyBoolean()); } private void mockPortraitDisplay(DisplayContent displayContent) { doAnswer(invocation -> { DisplayInfo displayInfo = new DisplayInfo(); displayContent.getDisplay().getDisplayInfo(displayInfo); displayInfo.rotation = Surface.ROTATION_90; // Set height and width so that the natural orientation (when rotation is 0) is // portrait. displayInfo.logicalHeight = 600; displayInfo.logicalWidth = 800; return displayInfo; }).when(displayContent.mWmService.mDisplayManagerInternal).getDisplayInfo(anyInt()); } } }
services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +58 −5 Original line number Diff line number Diff line Loading @@ -223,10 +223,13 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { setDisplayRotation(Surface.ROTATION_270); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity); callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, /* lastLetterbox= */ false); mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity); // Activity is letterboxed from the previous configuration change. callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, /* lastLetterbox= */ true); assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); assertActivityRefreshRequested(/* refreshRequested */ true); Loading Loading @@ -262,6 +265,48 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { mActivity)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ false); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); oldConfiguration.windowConfiguration.setDisplayRotation(0); newConfiguration.windowConfiguration.setDisplayRotation(90); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); Configuration newConfiguration = createConfiguration(/* letterbox= */ true); oldConfiguration.windowConfiguration.setDisplayRotation(0); newConfiguration.windowConfiguration.setDisplayRotation(0); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, oldConfiguration)); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() Loading Loading @@ -306,6 +351,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() { configureActivity(SCREEN_ORIENTATION_FULL_USER); Loading @@ -318,6 +364,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; Loading @@ -331,8 +378,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { /* delta= */ 0.001); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final float configAspectRatio = 1.5f; Loading Loading @@ -411,9 +458,15 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } private void callOnActivityConfigurationChanging(ActivityRecord activity) { callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true, /* lastLetterbox= */false); } private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew, boolean lastLetterbox) { mActivityRefresher.onActivityConfigurationChanging(activity, /* newConfig */ createConfiguration(/*letterbox=*/ true), /* lastReportedConfig */ createConfiguration(/*letterbox=*/ false)); /* newConfig */ createConfiguration(letterboxNew), /* lastReportedConfig */ createConfiguration(lastLetterbox)); } private Configuration createConfiguration(boolean letterbox) { Loading