diff options
5 files changed, 172 insertions, 48 deletions
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java index 1d00136ccfe1..8c5689c1ef57 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java @@ -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; @@ -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. diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 69421d0d5c96..8d8424820990 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -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)); } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 4f0cbf9ca6bd..2a0252ab06fd 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -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; @@ -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; @@ -64,8 +67,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa @NonNull private final CameraStateMonitor mCameraStateMonitor; - private boolean mIsCameraCompatTreatmentPending = false; - @Nullable private Task mCameraTask; @@ -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; } /** @@ -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 @@ -167,7 +186,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } } mCameraTask = null; - mIsCameraCompatTreatmentPending = false; return true; } @@ -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) { @@ -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. @@ -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. @@ -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; } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java index d8373c5dc3d6..50c2c2fd926c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java @@ -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; @@ -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( @@ -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(); @@ -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) { @@ -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()); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index a8ccf95e1bb5..a07fd235d14f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -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); @@ -264,6 +267,48 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @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() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -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); @@ -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; @@ -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; @@ -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) { |