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

Commit 4a445658 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Defer orientation for transient launch with immersive app policy

Because com.android.quickstep.util.RecentsOrientedState doesn't
support well for rotated app on large screen (the TaskView will
rotate unexpectedly), make the transition becomes 2 steps.
e.g. swiping an auto pip from landscape to portrait, it will play
pip animation in landscape. And then play a screen rotation from
landscape to portrait.

This also fixes suddenly orientation change if immersive rotation
policy is enabled when clicking navigation bar that launches recents.

Bug: 378422563
Flag: EXEMPT bugfix
Test: atest DisplayRotationImmersiveAppCompatPolicyTests# \
            testDeferOrientationUpdate
Test: Enable large screen env:
  config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled
      adb shell cmd window set-ignore-orientation-request 1
      adb shell wm size 2200x1600
      Enable auto-rotation
      Launch a fixed orientation fullscreen landscape app (hide bars).
      Rotate device 90 degree.
      Swipe up navigation bar or press navigation button.
      The orientation should not be changed until the
      transition of navigation is done.

Change-Id: I26db2ff64d2464b0ec9559f0416e86ab5d09f3d4
parent 247a20f2
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -485,6 +485,9 @@ public class DisplayRotation {
            if (isDefaultDisplay) {
                updateOrientationListenerLw();
            }
        } else if (mCompatPolicyForImmersiveApps != null
                && mCompatPolicyForImmersiveApps.deferOrientationUpdate()) {
            return false;
        }
        return updateRotationUnchecked(forceUpdate);
    }
+34 −0
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@
package com.android.server.wm;

import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
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 com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration.Orientation;
@@ -65,6 +68,37 @@ final class DisplayRotationImmersiveAppCompatPolicy {
        mDisplayContent = displayContent;
    }

    /**
     * Returns {@code true} if the orientation update should be skipped and it will update when
     * transition is done. This is to keep the orientation which was preserved by
     * {@link #isRotationLockEnforced} from being changed by a transient launch (i.e. recents).
     */
    boolean deferOrientationUpdate() {
        if (mDisplayRotation.getUserRotation() != USER_ROTATION_FREE
                || mDisplayRotation.getLastOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) {
            return false;
        }
        final WindowOrientationListener orientationListener =
                mDisplayRotation.getOrientationListener();
        if (orientationListener == null
                || orientationListener.getProposedRotation() == mDisplayRotation.getRotation()) {
            return false;
        }
        // The above conditions mean that isRotationLockEnforced might have taken effect:
        // Auto-rotation is enabled and the proposed rotation is not applied.
        // Then the update should defer until the transition idle to avoid disturbing animation.
        if (!mDisplayContent.mTransitionController.hasTransientLaunch(mDisplayContent)) {
            return false;
        }
        mDisplayContent.mTransitionController.mStateValidators.add(() -> {
            if (!isRotationLockEnforcedLocked(orientationListener.getProposedRotation())) {
                mDisplayContent.mWmService.updateRotation(false /* alwaysSendConfiguration */,
                        false /* forceRelayout */);
            }
        });
        return true;
    }

    /**
     * Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
     * the top activity configuration and proposed screen rotation.
+23 −0
Original line number Diff line number Diff line
@@ -20,16 +20,19 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;

import android.platform.test.annotations.Presubmit;
import android.view.Surface;
@@ -54,6 +57,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas

    private DisplayRotationImmersiveAppCompatPolicy mPolicy;

    private DisplayRotation mMockDisplayRotation;
    private AppCompatConfiguration mMockAppCompatConfiguration;
    private ActivityRecord mMockActivityRecord;
    private Task mMockTask;
@@ -98,6 +102,7 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
        when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_90)).thenReturn(true);
        when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_180)).thenReturn(false);
        when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_270)).thenReturn(true);
        mMockDisplayRotation = mockDisplayRotation;

        return mockDisplayRotation;
    }
@@ -195,6 +200,24 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
        assertIsRotationLockEnforcedReturnsFalseForAllRotations();
    }

    @Test
    public void testDeferOrientationUpdate() {
        assertFalse(mPolicy.deferOrientationUpdate());

        doReturn(SCREEN_ORIENTATION_UNSPECIFIED).when(mMockDisplayRotation).getLastOrientation();
        final WindowOrientationListener orientationListener = mock(WindowOrientationListener.class);
        doReturn(Surface.ROTATION_90).when(orientationListener).getProposedRotation();
        doReturn(orientationListener).when(mMockDisplayRotation).getOrientationListener();
        spyOn(mDisplayContent.mTransitionController);
        doReturn(true).when(mDisplayContent.mTransitionController)
                .hasTransientLaunch(mDisplayContent);

        assertTrue(mPolicy.deferOrientationUpdate());
        mDisplayContent.mTransitionController.mStateValidators.getFirst().run();

        verify(mWm).updateRotation(false, false);
    }

    @Test
    public void testRotationChoiceEnforcedOnly_nullTopRunningActivity_lockNotEnforced() {
        when(mDisplayContent.topRunningActivity()).thenReturn(null);