From 677f71a8532bdeae1252d1ad90a7e2b943925cff Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Fri, 15 Apr 2022 14:31:30 +0800 Subject: [PATCH] Handle orientation behind for fixed rotation The activity which decides the display orientation may not always be the top activity, such as declaring "behind" orientation. And because recents animation will be canceled when display orientation changes, it causes to fail swiping to a orientation-behind app. So simply get the real top if the orientation source is not visible. Bug: 226298709 Test: atest DisplayContentTests#testOrientationBehind Test: With TaskA{portrait}, TaskB{behind, landscape}, it should be able to use quickstep to swipe from TaskA to TaskB Change-Id: I4cf9f3ab977ac7c8b22eb4c37218a5d2019bfabc --- .../com/android/server/wm/DisplayContent.java | 30 +++++++++++++++---- .../server/wm/DisplayContentTests.java | 23 ++++++++++++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4660757024095..b3fafa2137660 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1575,8 +1575,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAtmService.getTaskChangeNotificationController() .notifyTaskRequestedOrientationChanged(task.mTaskId, orientation); } - // Currently there is no use case from non-activity. - if (handleTopActivityLaunchingInDifferentOrientation(r, true /* checkOpening */)) { + // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND. + final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r; + if (handleTopActivityLaunchingInDifferentOrientation( + topCandidate, r, true /* checkOpening */)) { // Display orientation should be deferred until the top fixed rotation is finished. return false; } @@ -1593,7 +1595,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** * Returns a valid rotation if the activity can use different orientation than the display. - * Otherwise {@link #ROTATION_UNDEFINED}. + * Otherwise {@link android.app.WindowConfiguration#ROTATION_UNDEFINED}. */ @Rotation int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) { @@ -1603,6 +1605,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { return ROTATION_UNDEFINED; } + if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + final ActivityRecord nextCandidate = getActivity( + a -> a.mOrientation != SCREEN_ORIENTATION_UNSET + && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, + r, false /* includeBoundary */, true /* traverseTopToBottom */); + if (nextCandidate != null) { + r = nextCandidate; + } + } if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */) == getConfiguration().orientation) { return ROTATION_UNDEFINED; @@ -1616,18 +1627,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return rotation; } + boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r, + boolean checkOpening) { + return handleTopActivityLaunchingInDifferentOrientation(r, r, checkOpening); + } + /** * We need to keep display rotation fixed for a while when the activity in different orientation * is launching until the launch animation is done to avoid showing the previous activity * inadvertently in a wrong orientation. * * @param r The launching activity which may change display orientation. + * @param orientationSrc It may be different from {@param r} if the launching activity uses + * "behind" orientation. * @param checkOpening Whether to check if the activity is animating by transition. Set to * {@code true} if the caller is not sure whether the activity is launching. * @return {@code true} if the fixed rotation is started. */ - boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r, - boolean checkOpening) { + private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r, + @NonNull ActivityRecord orientationSrc, boolean checkOpening) { if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { return false; } @@ -1676,7 +1694,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // animation is not running (it may be swiping to home). return false; } - final int rotation = rotationForActivityInDifferentOrientation(r); + final int rotation = rotationForActivityInDifferentOrientation(orientationSrc); if (rotation == ROTATION_UNDEFINED) { // The display rotation won't be changed by current top activity. The client side // adjustments of previous rotated activity should be cleared earlier. Otherwise if diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 118f159bee7bc..09f3b0c26a2fb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; 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; @@ -1092,6 +1093,25 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(ROTATION_180, dc.getRotation()); } + @Test + public void testOrientationBehind() { + final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true) + .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build(); + prev.mVisibleRequested = false; + final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true) + .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build(); + assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED, + mDisplayContent.rotationForActivityInDifferentOrientation(top)); + + mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0); + top.setVisibility(true); + mDisplayContent.updateOrientation(); + // The top uses "behind", so the orientation is decided by the previous. + assertEquals(prev, mDisplayContent.getLastOrientationSource()); + // The top will use the rotation from "prev" with fixed rotation. + assertTrue(top.hasFixedRotationTransform()); + } + @Test public void testFixedToUserRotationChanged() { final DisplayContent dc = createNewDisplay(); @@ -1650,8 +1670,7 @@ public class DisplayContentTests extends WindowTestsBase { // The condition should reject using fixed rotation because the resumed client in real case // might get display info immediately. And the fixed rotation adjustments haven't arrived // client side so the info may be inconsistent with the requested orientation. - verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app), - eq(true) /* checkOpening */); + verify(mDisplayContent).updateOrientation(eq(app), anyBoolean()); assertFalse(app.isFixedRotationTransforming()); assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp()); }