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

Commit d4957437 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Extensive handling of fixed rotation launching app

This fixes a case that display does not rotate after multiple
activities in a different rotation than display are launched.

- Link the transform state from the existing one to the new
  launching activity. So the heavy duplicated calculation is
  omitted and they can be recognized as a group to continue
  to update display rotation.
- Add a dedicated transition listener so it will not miss to
  handle display rotation if the reported token is different.
  That avoids leakage if somehow the transition is not notified
  then the listener is not unregistered. The path to finish
  fixed rotation is also simplified because there is always a
  transition listener to handle the incoming record.

Fixes: 154911677
Test: atest DisplayContentTests#testApplyTopFixedRotationTransform

Change-Id: Id370d7980d11553905b6a051482e3765ed61dc39
parent a077a8a0
Loading
Loading
Loading
Loading
+61 −33
Original line number Diff line number Diff line
@@ -498,6 +498,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
     */
    ActivityRecord mFixedRotationLaunchingApp;

    final FixedRotationTransitionListener mFixedRotationTransitionListener =
            new FixedRotationTransitionListener();

    /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
    final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();

@@ -928,6 +931,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo

        mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
        mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
        mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
        mAppTransitionController = new AppTransitionController(mWmService, this);
        mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);

@@ -1383,7 +1387,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                orientationSource != null ? orientationSource.asActivityRecord() : null;
        // Currently there is no use case from non-activity.
        if (r != null && handleTopActivityLaunchingInDifferentOrientation(r)) {
            mFixedRotationLaunchingApp = r;
            // Display orientation should be deferred until the top fixed rotation is finished.
            return false;
        }
@@ -1448,47 +1451,44 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            return false;
        }

        startFixedRotationTransform(r, rotation);
        mAppTransition.registerListenerLocked(new WindowManagerInternal.AppTransitionListener() {
            void done() {
                r.finishFixedRotationTransform();
                mAppTransition.unregisterListener(this);
            }

            @Override
            public void onAppTransitionFinishedLocked(IBinder token) {
                if (token == r.token) {
                    done();
                }
            }

            @Override
            public void onAppTransitionCancelledLocked(int transit) {
                done();
        final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp;
        if (prevRotatedLaunchingApp != null
                && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation
                // It is animating so we can expect there will have a transition callback.
                && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) {
            // It may be the case that multiple activities launch consecutively. Because their
            // rotation are the same, the transformed state can be shared to avoid duplicating
            // the heavy operations. This also benefits that the states of multiple activities
            // are handled together.
            r.linkFixedRotationTransform(prevRotatedLaunchingApp);
            return true;
        }

            @Override
            public void onAppTransitionTimeoutLocked() {
                done();
        startFixedRotationTransform(r, rotation);
        mFixedRotationLaunchingApp = r;
        if (prevRotatedLaunchingApp != null) {
            prevRotatedLaunchingApp.finishFixedRotationTransform();
        }
        });
        return true;
    }

    /** @return {@code true} if the display orientation will be changed. */
    boolean continueUpdateOrientationForDiffOrienLaunchingApp(WindowToken token) {
        if (token != mFixedRotationLaunchingApp) {
            return false;
    /**
     * Continue updating the orientation change of display if it was deferred by a top activity
     * launched in a different orientation.
     */
    void continueUpdateOrientationForDiffOrienLaunchingApp() {
        if (mFixedRotationLaunchingApp == null) {
            return;
        }
        // Update directly because the app which will change the orientation of display is ready.
        if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
            sendNewConfiguration();
            return true;
            return;
        }
        // The display won't rotate (e.g. the orientation from sensor has updated again before
        // applying rotation to display), so clear it to stop using seamless rotation.
        mFixedRotationLaunchingApp.finishFixedRotationTransform();
        mFixedRotationLaunchingApp = null;
        return false;
    }

    private void startFixedRotationTransform(WindowToken token, int rotation) {
@@ -5184,7 +5184,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
        final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();
        if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) {
            applyRotationAndClearFixedRotation(currRotation, overrideRotation);
            applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
        }
        mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
@@ -5200,7 +5200,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
     * fixed rotation transform also needs to be cleared to make sure the rotated activity fits
     * the display naturally.
     */
    private void applyRotationAndClearFixedRotation(int oldRotation, int newRotation) {
    private void applyRotationAndFinishFixedRotation(int oldRotation, int newRotation) {
        if (mFixedRotationLaunchingApp == null) {
            applyRotation(oldRotation, newRotation);
            return;
@@ -5219,7 +5219,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            }
        });

        mFixedRotationLaunchingApp.clearFixedRotationTransform(
        mFixedRotationLaunchingApp.finishFixedRotationTransform(
                () -> applyRotation(oldRotation, newRotation));
        mFixedRotationLaunchingApp = null;
    }
@@ -5494,6 +5494,34 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        });
    }

    /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
    class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {

        @Override
        public void onAppTransitionFinishedLocked(IBinder token) {
            final ActivityRecord r = getActivityRecord(token);
            if (r == null) {
                return;
            }
            if (mFixedRotationLaunchingApp != null
                    && mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) {
                continueUpdateOrientationForDiffOrienLaunchingApp();
            } else {
                r.finishFixedRotationTransform();
            }
        }

        @Override
        public void onAppTransitionCancelledLocked(int transit) {
            continueUpdateOrientationForDiffOrienLaunchingApp();
        }

        @Override
        public void onAppTransitionTimeoutLocked() {
            continueUpdateOrientationForDiffOrienLaunchingApp();
        }
    }

    class RemoteInsetsControlTarget implements InsetsControlTarget {
        private final IDisplayWindowInsetsController mRemoteInsetsController;

+14 −22
Original line number Diff line number Diff line
@@ -119,7 +119,6 @@ class WindowToken extends WindowContainer<WindowState> {
     * rotated by the given rotated display info, frames and insets.
     */
    private static class FixedRotationTransformState {
        final WindowToken mOwner;
        final DisplayInfo mDisplayInfo;
        final DisplayFrames mDisplayFrames;
        final InsetsState mInsetsState;
@@ -133,10 +132,9 @@ class WindowToken extends WindowContainer<WindowState> {
        final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3);
        boolean mIsTransforming = true;

        FixedRotationTransformState(WindowToken owner, DisplayInfo rotatedDisplayInfo,
        FixedRotationTransformState(DisplayInfo rotatedDisplayInfo,
                DisplayFrames rotatedDisplayFrames, InsetsState rotatedInsetsState,
                Configuration rotatedConfig, int currentRotation) {
            mOwner = owner;
            mDisplayInfo = rotatedDisplayInfo;
            mDisplayFrames = rotatedDisplayFrames;
            mInsetsState = rotatedInsetsState;
@@ -482,6 +480,14 @@ class WindowToken extends WindowContainer<WindowState> {
        return mFixedRotationTransformState != null;
    }

    /** Returns {@code true} if the given token shares the same transform. */
    boolean hasFixedRotationTransform(WindowToken token) {
        if (mFixedRotationTransformState == null || token == null) {
            return false;
        }
        return this == token || mFixedRotationTransformState == token.mFixedRotationTransformState;
    }

    boolean isFinishingFixedRotationTransform() {
        return mFixedRotationTransformState != null
                && !mFixedRotationTransformState.mIsTransforming;
@@ -520,15 +526,14 @@ class WindowToken extends WindowContainer<WindowState> {
        final InsetsState insetsState = new InsetsState();
        mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, insetsState,
                mDisplayContent.getConfiguration().uiMode);
        mFixedRotationTransformState = new FixedRotationTransformState(this, info, displayFrames,
        mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
                insetsState, new Configuration(config), mDisplayContent.getRotation());
        onConfigurationChanged(getParent().getConfiguration());
    }

    /**
     * Reuses the {@link FixedRotationTransformState} (if any) from the other WindowToken to this
     * one. This takes the same effect as {@link #applyFixedRotationTransform}, but the linked state
     * can only be cleared by the state owner.
     * one. This takes the same effect as {@link #applyFixedRotationTransform}.
     */
    void linkFixedRotationTransform(WindowToken other) {
        if (mFixedRotationTransformState != null) {
@@ -543,28 +548,15 @@ class WindowToken extends WindowContainer<WindowState> {
        onConfigurationChanged(getParent().getConfiguration());
    }

    /**
     * Finishes the transform and continue updating the orientation change of display. Only the
     * state owner can finish the transform state.
     */
    void finishFixedRotationTransform() {
        if (mFixedRotationTransformState == null || mFixedRotationTransformState.mOwner != this) {
            return;
        }
        final boolean changed =
                mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(this);
        // If it is not the launching app or the display is not rotated, make sure the transform is
        // cleared and the configuration is restored from parent.
        if (!changed) {
            clearFixedRotationTransform(null /* applyDisplayRotation */);
        }
        finishFixedRotationTransform(null /* applyDisplayRotation */);
    }

    /**
     * Clears the transform and apply display rotation if the action is given. If the display will
     * Finishes the transform and apply display rotation if the action is given. If the display will
     * not rotate, the transformed containers are restored to their original states.
     */
    void clearFixedRotationTransform(Runnable applyDisplayRotation) {
    void finishFixedRotationTransform(Runnable applyDisplayRotation) {
        final FixedRotationTransformState state = mFixedRotationTransformState;
        if (state == null) {
            return;
+24 −8
Original line number Diff line number Diff line
@@ -790,9 +790,7 @@ public class DisplayContentTests extends WindowTestsBase {
        final DisplayContent dc = createNewDisplay();
        dc.getDisplayRotation().setFixedToUserRotation(
                IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
        final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
                ? SCREEN_ORIENTATION_PORTRAIT
                : SCREEN_ORIENTATION_LANDSCAPE;
        final int newOrientation = getRotatedOrientation(dc);

        final ActivityStack stack =
                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
@@ -812,9 +810,7 @@ public class DisplayContentTests extends WindowTestsBase {
        final DisplayContent dc = createNewDisplay();
        dc.getDisplayRotation().setFixedToUserRotation(
                IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
        final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
                ? SCREEN_ORIENTATION_PORTRAIT
                : SCREEN_ORIENTATION_LANDSCAPE;
        final int newOrientation = getRotatedOrientation(dc);

        final ActivityStack stack =
                new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootWindowContainer)
@@ -1083,7 +1079,8 @@ public class DisplayContentTests extends WindowTestsBase {
        mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
                false /* alwaysKeepCurrent */);
        mDisplayContent.mOpeningApps.add(app);
        app.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
        final int newOrientation = getRotatedOrientation(mDisplayContent);
        app.setRequestedOrientation(newOrientation);

        assertTrue(app.isFixedRotationTransforming());
        assertTrue(mDisplayContent.getDisplayRotation().shouldRotateSeamlessly(
@@ -1124,12 +1121,25 @@ public class DisplayContentTests extends WindowTestsBase {
        mWallpaperWindow.mToken.onAnimationLeashCreated(t, null /* leash */);
        verify(t, never()).setPosition(any(), eq(0), eq(0));

        // Launch another activity before the transition is finished.
        final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
                .setDisplay(mDisplayContent).build().getTopMostActivity();
        mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
                false /* alwaysKeepCurrent */);
        mDisplayContent.mOpeningApps.add(app2);
        app2.setRequestedOrientation(newOrientation);

        // The activity should share the same transform state as the existing one.
        assertTrue(app.hasFixedRotationTransform(app2));

        // The display should be rotated after the launch is finished.
        mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);

        // The animation in old rotation should be cancelled.
        assertFalse(closingApp.isAnimating());
        // The display should be rotated after the launch is finished.
        // The fixed rotation should be cleared and the new rotation is applied to display.
        assertFalse(app.hasFixedRotationTransform());
        assertFalse(app2.hasFixedRotationTransform());
        assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
    }

@@ -1292,6 +1302,12 @@ public class DisplayContentTests extends WindowTestsBase {
        assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop)));
    }

    private static int getRotatedOrientation(DisplayContent dc) {
        return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
                ? SCREEN_ORIENTATION_PORTRAIT
                : SCREEN_ORIENTATION_LANDSCAPE;
    }

    private static List<WindowState> reverseList(List<WindowState> list) {
        final ArrayList<WindowState> result = new ArrayList<>(list);
        Collections.reverse(result);
+0 −4
Original line number Diff line number Diff line
@@ -401,10 +401,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
        assertEquals(Configuration.ORIENTATION_PORTRAIT,
                wallpapers.get(0).getConfiguration().orientation);

        // Wallpaper's transform state is controlled by home, so the invocation should be no-op.
        wallpaperWindowToken.finishFixedRotationTransform();
        assertTrue(wallpaperWindowToken.hasFixedRotationTransform());

        // Wallpaper's transform state should be cleared with home.
        homeActivity.finishFixedRotationTransform();
        assertFalse(wallpaperWindowToken.hasFixedRotationTransform());
+2 −2
Original line number Diff line number Diff line
@@ -136,7 +136,7 @@ public class WindowTokenTests extends WindowTestsBase {
    }

    @Test
    public void testClearFixedRotationTransform() {
    public void testFinishFixedRotationTransform() {
        final WindowToken appToken = mAppWindow.mToken;
        final WindowToken wallpaperToken = mWallpaperWindow.mToken;
        final Configuration config = new Configuration(mDisplayContent.getConfiguration());
@@ -152,7 +152,7 @@ public class WindowTokenTests extends WindowTestsBase {
        assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());

        // The display doesn't rotate, the transformation will be canceled.
        mAppWindow.mToken.clearFixedRotationTransform(null /* applyDisplayRotation */);
        mAppWindow.mToken.finishFixedRotationTransform();

        // The window tokens should restore to the original rotation.
        assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());