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

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

Ensure activity configuration when applying WCT with display change

This fixes two cases:
1. Because WOC#applyTransaction always defers visibility(config) update,
the ensureActivitiesVisible in ATMS#ensureConfigAndVisibilityAfterUpdate
is always no-op, if there are non-top visible activities, their
configuration won't be delivered to clients in time.
E.g. Launch a translucent task and rotate device, then if the activity
behind is slow to relaunch, it may flash back content.

2. ATMS#ensureConfigAndVisibilityAfterUpdate always updates config for
top activity. When rotating display with split-screen, the WCT also
contains the operation to set task bounds. Then it will cause redundant
activity relaunch.

Fix: 313910429
Test: atest DisplayContentTests#testShellTransitRotation

Change-Id: I8c2e4d1d7ac5d3afe832ced31588f15cc24b8d81
parent 1f50aa3c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -5280,6 +5280,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {

    /** Applies latest configuration and/or visibility updates if needed. */
    boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
        if (starting == null && mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
            return true;
        }
        boolean kept = true;
        final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
        // mainRootTask is null during startup.
+23 −2
Original line number Diff line number Diff line
@@ -2836,12 +2836,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        return false;
    }

    /** Applies the new configuration for the changed displays. */
    void applyDisplayChangeIfNeeded() {
    /**
     * Applies the new configuration for the changed displays. Returns the activities that should
     * check whether to deliver the new configuration to clients.
     */
    @Nullable
    ArrayList<ActivityRecord> applyDisplayChangeIfNeeded() {
        ArrayList<ActivityRecord> activitiesMayChange = null;
        for (int i = mParticipants.size() - 1; i >= 0; --i) {
            final WindowContainer<?> wc = mParticipants.valueAt(i);
            final DisplayContent dc = wc.asDisplayContent();
            if (dc == null || !mChanges.get(dc).hasChanged()) continue;
            final int originalSeq = dc.getConfiguration().seq;
            dc.sendNewConfiguration();
            // Set to ready if no other change controls the ready state. But if there is, such as
            // if an activity is pausing, it will call setReady(ar, false) and wait for the next
@@ -2850,7 +2856,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            if (!mReadyTrackerOld.mUsed) {
                setReady(dc, true);
            }
            if (originalSeq == dc.getConfiguration().seq) continue;
            // If the update is deferred, sendNewConfiguration won't deliver new configuration to
            // clients, then it is the caller's responsibility to deliver the changes.
            if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
                if (activitiesMayChange == null) {
                    activitiesMayChange = new ArrayList<>();
                }
                final ArrayList<ActivityRecord> visibleActivities = activitiesMayChange;
                dc.forAllActivities(r -> {
                    if (r.isVisibleRequested()) {
                        visibleActivities.add(r);
                    }
                });
            }
        }
        return activitiesMayChange;
    }

    boolean getLegacyIsReady() {
+19 −2
Original line number Diff line number Diff line
@@ -568,8 +568,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
        mService.deferWindowLayout();
        mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
        try {
            if (transition != null) {
                transition.applyDisplayChangeIfNeeded();
            final ArrayList<ActivityRecord> activitiesMayChange =
                    transition != null ? transition.applyDisplayChangeIfNeeded() : null;
            if (activitiesMayChange != null) {
                effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
            }
            final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
            final int hopSize = hops.size();
@@ -693,8 +695,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
                    haveConfigChanges.valueAt(i).forAllActivities(r -> {
                        r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
                        if (activitiesMayChange != null) {
                            activitiesMayChange.remove(r);
                        }
                    });
                }
                // TODO(b/258618073): Combine with haveConfigChanges after confirming that there
                //  is no problem to always preserve window. Currently this uses the parameters
                //  as ATMS#ensureConfigAndVisibilityAfterUpdate.
                if (activitiesMayChange != null) {
                    for (int i = activitiesMayChange.size() - 1; i >= 0; --i) {
                        final ActivityRecord ar = activitiesMayChange.get(i);
                        if (!ar.isVisibleRequested()) continue;
                        ar.ensureActivityConfiguration(0 /* globalChanges */,
                                !PRESERVE_WINDOWS, true /* ignoreVisibility */,
                                false /* isRequestedOrientationChanged */);
                    }
                }
            }

            if (effects != 0) {
+13 −10
Original line number Diff line number Diff line
@@ -2086,15 +2086,17 @@ public class DisplayContentTests extends WindowTestsBase {

    @Test
    public void testShellTransitRotation() {
        DisplayContent dc = createNewDisplay();
        dc.setLastHasContent();
        final DisplayContent dc = mDisplayContent;
        // Create 2 visible activities to verify that they can both receive the new configuration.
        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
        doReturn(true).when(activity1).isSyncFinished(any());
        doReturn(true).when(activity2).isSyncFinished(any());

        final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
        final DisplayRotation dr = dc.getDisplayRotation();
        doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
        // Rotate 180 degree so the display doesn't have configuration change. This condition is
        // used for the later verification of stop-freezing (without setting mWaitingForConfig).
        doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
        spyOn(dr);
        doReturn((dr.getRotation() + 1) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
        mWm.mDisplayChangeController =
                new IDisplayChangeWindowController.Stub() {
                    @Override
@@ -2109,11 +2111,8 @@ public class DisplayContentTests extends WindowTestsBase {
                    }
                };

        // kill any existing rotation animation (vestigial from test setup).
        dc.setRotationAnimation(null);

        final int origRot = dc.getConfiguration().windowConfiguration.getRotation();

        dc.setLastHasContent();
        mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
        // Should create a transition request without performing rotation
        assertNotNull(testPlayer.mLastRequest);
@@ -2122,6 +2121,10 @@ public class DisplayContentTests extends WindowTestsBase {
        // Once transition starts, rotation is applied and transition shows DC rotating.
        testPlayer.startTransition();
        waitUntilHandlersIdle();
        verify(activity1).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
                anyBoolean());
        verify(activity2).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
                anyBoolean());
        assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
        assertNotNull(testPlayer.mLastReady);
        assertTrue(testPlayer.mController.isPlaying());