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

Commit 211e1dd8 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Keep visible requested for transient hide activities

If a process doesn't have other active component and its activity
is not visible requested, it will become background state and may
lose the access of various system services.

The invisible requested will also cause other animation issues
such as incorrect dim layer, window surface position. Because
isGoneForLayout will return false by the visible state.

By checking isTransientHide in TaskFragment#getVisibility,
the task can keep visible even if it is occluded by the transient
launch target. And then add FLAG_ABOVE_TRANSIENT_LAUNCH to the
tree of transient-hide task so the ChangeInfo will always report
as TRANSIT_TO_BACK.

When the transient transition is finished, if the transient launch
target is visible, then update the visibility to make the
transient-hide activities to be invisible requested and add the
activities to stopping list. So when checking the visibility
participants, it can continue to commit the state.

Also add a condition for mAvoidMoveToFront for the case that the
moving-to-back task may be moved to front again after transient
launch if the app uses a trampoline activity.
- Previously during the transient transition, the trampoline launch
  will be blocked by BAL because invisible.
- In legacy transition, the task is already on top (recent is
  launch-behind) and finish-recents-animation will move home to front.
Now it will launch the new activity into its task without moving
the task to front, e.g. during the swipe-return-to-home animation,
the going-to-invisible activity launches next activity, it will
still stay at home. And then launch the app again, the latest
new activity on its task can be resumed directly.

Fix: 269577449
Fix: 271282193
Test: atest TransitionTests#testTransientLaunch
Change-Id: I887f63a4985804a59e6602817dce6aec0e6f2c69
parent 4893b904
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -1680,6 +1680,11 @@ class ActivityStarter {
                targetTask.removeImmediately("bulky-task");
                return START_ABORTED;
            }
            // When running transient transition, the transient launch target should keep on top.
            // So disallow the transient hide activity to move itself to front, e.g. trampoline.
            if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
                mAvoidMoveToFront = true;
            }
            mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
        }

@@ -1796,7 +1801,7 @@ class ActivityStarter {
                // root-task to the will not update the focused root-task.  If starting the new
                // activity now allows the task root-task to be focusable, then ensure that we
                // now update the focused root-task accordingly.
                if (mTargetRootTask.isTopActivityFocusable()
                if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
                        && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
                    mTargetRootTask.moveToFront("startActivityInner");
                }
+4 −0
Original line number Diff line number Diff line
@@ -1012,6 +1012,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        if (isTopActivityLaunchedBehind()) {
            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
        }
        final Task thisTask = asTask();
        if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
        }

        boolean gotTranslucentFullscreen = false;
        boolean gotTranslucentAdjacent = false;
+54 −24
Original line number Diff line number Diff line
@@ -194,6 +194,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     */
    private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;

    /**
     * The tasks that may be occluded by the transient activity. Assume the task stack is
     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
     * task, and [B, C] are the transient-hide tasks.
     */
    private ArrayList<Task> mTransientHideTasks;

    /** Custom activity-level animation options and callbacks. */
    private TransitionInfo.AnimationOptions mOverrideOptions;
    private IRemoteCallback mClientAnimationStartCallback = null;
@@ -265,35 +272,51 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
    void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
        if (mTransientLaunches == null) {
            mTransientLaunches = new ArrayMap<>();
            mTransientHideTasks = new ArrayList<>();
        }
        mTransientLaunches.put(activity, restoreBelow);
        setTransientLaunchToChanges(activity);

        if (restoreBelow != null) {
            final ChangeInfo info = mChanges.get(restoreBelow);
            if (info != null) {
                info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
            // Collect all visible activities which can be occluded by the transient activity to
            // make sure they are in the participants so their visibilities can be updated when
            // finishing transition.
            ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
                if (t.isVisibleRequested() && !t.isAlwaysOnTop()
                        && !t.getWindowConfiguration().tasksAreFloating()) {
                    if (t.isRootTask()) {
                        mTransientHideTasks.add(t);
                    }
                    if (t.isLeafTask()) {
                        t.forAllActivities(r -> {
                            if (r.isVisibleRequested()) {
                                collect(r);
                            }
                        });
                    }
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
                + "transient-launch", mSyncId, activity);
                }

    boolean isTransientHide(@NonNull Task task) {
        if (mTransientLaunches == null) return false;
        for (int i = 0; i < mTransientLaunches.size(); ++i) {
            if (mTransientLaunches.valueAt(i) == task) {
                return true;
                return t == restoreBelow;
            });
            // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
            // so ChangeInfo#hasChanged() can return true to report the transition info.
            for (int i = mChanges.size() - 1; i >= 0; --i) {
                final WindowContainer<?> wc = mChanges.keyAt(i);
                if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
                if (isInTransientHide(wc)) {
                    mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
                }
            }
        return false;
        }
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
                + "transient-launch", mSyncId, activity);
    }

    /** @return whether `wc` is a descendent of a transient-hide window. */
    boolean isInTransientHide(@NonNull WindowContainer wc) {
        if (mTransientLaunches == null) return false;
        for (int i = 0; i < mTransientLaunches.size(); ++i) {
            if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
        if (mTransientHideTasks == null) return false;
        for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
            final Task task = mTransientHideTasks.get(i);
            if (wc == task || wc.isDescendantOf(task)) {
                return true;
            }
        }
@@ -816,6 +839,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        }
        mController.mFinishingTransition = this;

        if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
            // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
            // the update to make the activities in the tasks invisible-requested, then the next
            // step can continue to commit the visibility.
            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
                    0 /* configChanges */, true /* preserveWindows */);
        }

        boolean hasParticipatedDisplay = false;
        boolean hasVisibleTransientLaunch = false;
        // Commit all going-invisible containers
@@ -1175,15 +1206,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {

        // Record windowtokens (activity/wallpaper) that are expected to be visible after the
        // transition animation. This will be used in finishTransition to prevent prematurely
        // committing visibility.
        // committing visibility. Skip transient launches since those are only temporarily visible.
        if (mTransientLaunches == null) {
            for (int i = mParticipants.size() - 1; i >= 0; --i) {
                final WindowContainer wc = mParticipants.valueAt(i);
                if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
            // don't include transient launches, though, since those are only temporarily visible.
            if (mTransientLaunches != null && wc.asActivityRecord() != null
                    && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
                mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
            }
        }

        // Take task snapshots before the animation so that we can capture IME before it gets
        // transferred. If transition is transient, IME won't be moved during the transition and
+2 −2
Original line number Diff line number Diff line
@@ -366,11 +366,11 @@ class TransitionController {
    }

    boolean isTransientHide(@NonNull Task task) {
        if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
        if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
            return true;
        }
        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
            if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
            if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
        }
        return false;
    }
+15 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -54,6 +55,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -1400,7 +1402,13 @@ public class TransitionTests extends WindowTestsBase {
        closeTransition.collectExistenceChange(activity1);
        closeTransition.collectExistenceChange(task2);
        closeTransition.collectExistenceChange(activity2);
        closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
        closeTransition.setTransientLaunch(activity2, task1);
        final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
        assertNotNull(task1ChangeInfo);
        assertTrue(task1ChangeInfo.hasChanged());
        final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
        assertNotNull(activity1ChangeInfo);
        assertTrue(activity1ChangeInfo.hasChanged());

        activity1.setVisibleRequested(false);
        activity2.setVisibleRequested(true);
@@ -1416,6 +1424,8 @@ public class TransitionTests extends WindowTestsBase {
        verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));

        enteringAnimReports.clear();
        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
                anyInt(), anyBoolean(), anyBoolean());
        final boolean[] wasInFinishingTransition = { false };
        controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
            @Override
@@ -1430,7 +1440,11 @@ public class TransitionTests extends WindowTestsBase {
        assertTrue(wasInFinishingTransition[0]);
        assertNull(controller.mFinishingTransition);

        assertTrue(activity2.isVisible());
        assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
        // Because task1 is occluded by task2, finishTransition should make activity1 invisible.
        assertFalse(activity1.isVisibleRequested());
        assertFalse(activity1.isVisible());
        assertFalse(activity1.app.hasActivityInVisibleTask());

        verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));