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

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

Allow finishing activity to transfer starting window

Assume an application launches multiple activities consecutively
in the same task, given as A, B, C. A is an opaque activity that
will start B and finish. B is a translucent activity that will
start C and finish.

The starting window of A won't transfer to B because B is translucent.
Then when C is looking for if there is existing starting window to
reuse, A has become finishing so the starting window won't involve in
transition. So there may be more than one transition.

Also
- Wrap startActivities with defer/continue layout to avoid
  applying intermediate states.
- Move post cleanup of transferring starting window back so the
  visibility can transfer before resetting because the from-activity
  was visible by previous transferring.

That makes the case can have an atomic-like transition so even the last
activity uses overridePendingTransition which may be redundant, the
previous active transition will ignore its request and the animation
will look smoother.

Fixes: 156298440
Test: atest AppWindowTokenTests# \
      testTransferStartingWindowFromFinishingActivity
Test: Use a sample app to simulate above steps. There should not
      have black screen when animating the last activity.
Change-Id: I54b7aa58a46a2f55d505904caa300fd44abd4d41
parent 148fb0e8
Loading
Loading
Loading
Loading
+3 −2
Original line number Original line Diff line number Diff line
@@ -3366,8 +3366,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                        "Removing starting %s from %s", tStartingWindow, fromActivity);
                        "Removing starting %s from %s", tStartingWindow, fromActivity);
                fromActivity.removeChild(tStartingWindow);
                fromActivity.removeChild(tStartingWindow);
                fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
                fromActivity.mVisibleSetFromTransferredStartingWindow = false;
                addWindow(tStartingWindow);
                addWindow(tStartingWindow);


                // Propagate other interesting state between the tokens. If the old token is displayed,
                // Propagate other interesting state between the tokens. If the old token is displayed,
@@ -3394,6 +3392,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                    // we've transferred the animation.
                    // we've transferred the animation.
                    mUseTransferredAnimation = true;
                    mUseTransferredAnimation = true;
                }
                }
                // Post cleanup after the visibility and animation are transferred.
                fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
                fromActivity.mVisibleSetFromTransferredStartingWindow = false;


                mWmService.updateFocusedWindowLocked(
                mWmService.updateFocusedWindowLocked(
                        UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
                        UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+1 −1
Original line number Original line Diff line number Diff line
@@ -2140,7 +2140,7 @@ class ActivityStack extends Task {
                // window manager to keep the previous window it had previously
                // window manager to keep the previous window it had previously
                // created, if it still had one.
                // created, if it still had one.
                Task prevTask = r.getTask();
                Task prevTask = r.getTask();
                ActivityRecord prev = prevTask.topRunningActivityWithStartingWindowLocked();
                ActivityRecord prev = prevTask.topActivityWithStartingWindow();
                if (prev != null) {
                if (prev != null) {
                    // We don't want to reuse the previous starting preview if:
                    // We don't want to reuse the previous starting preview if:
                    // (1) The current activity is in a different task.
                    // (1) The current activity is in a different task.
+24 −19
Original line number Original line Diff line number Diff line
@@ -467,6 +467,8 @@ public class ActivityStartController {
            final ActivityRecord[] outActivity = new ActivityRecord[1];
            final ActivityRecord[] outActivity = new ActivityRecord[1];
            // Lock the loop to ensure the activities launched in a sequence.
            // Lock the loop to ensure the activities launched in a sequence.
            synchronized (mService.mGlobalLock) {
            synchronized (mService.mGlobalLock) {
                mService.deferWindowLayout();
                try {
                    for (int i = 0; i < starters.length; i++) {
                    for (int i = 0; i < starters.length; i++) {
                        final int startResult = starters[i].setResultTo(resultTo)
                        final int startResult = starters[i].setResultTo(resultTo)
                                .setOutActivity(outActivity).execute();
                                .setOutActivity(outActivity).execute();
@@ -479,8 +481,8 @@ public class ActivityStartController {
                        }
                        }
                        final ActivityRecord started = outActivity[0];
                        final ActivityRecord started = outActivity[0];
                        if (started != null && started.getUid() == filterCallingUid) {
                        if (started != null && started.getUid() == filterCallingUid) {
                        // Only the started activity which has the same uid as the source caller can
                            // Only the started activity which has the same uid as the source caller
                        // be the caller of next activity.
                            // can be the caller of next activity.
                            resultTo = started.appToken;
                            resultTo = started.appToken;
                        } else {
                        } else {
                            resultTo = sourceResultTo;
                            resultTo = sourceResultTo;
@@ -490,6 +492,9 @@ public class ActivityStartController {
                            }
                            }
                        }
                        }
                    }
                    }
                } finally {
                    mService.continueWindowLayout();
                }
            }
            }
        } finally {
        } finally {
            Binder.restoreCallingIdentity(origId);
            Binder.restoreCallingIdentity(origId);
+2 −2
Original line number Original line Diff line number Diff line
@@ -1309,12 +1309,12 @@ class Task extends WindowContainer<WindowContainer> {
        return isUidPresent;
        return isUidPresent;
    }
    }


    ActivityRecord topRunningActivityWithStartingWindowLocked() {
    ActivityRecord topActivityWithStartingWindow() {
        if (getParent() == null) {
        if (getParent() == null) {
            return null;
            return null;
        }
        }
        return getActivity((r) -> r.mStartingWindowState == STARTING_WINDOW_SHOWN
        return getActivity((r) -> r.mStartingWindowState == STARTING_WINDOW_SHOWN
                && r.canBeTopRunning());
                && r.okToShowLocked());
    }
    }


    /**
    /**
+42 −0
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;


import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;


import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
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.mock;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
@@ -47,6 +48,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
@@ -390,6 +392,46 @@ public class AppWindowTokenTests extends WindowTestsBase {
                onAnimationFinishedCallback));
                onAnimationFinishedCallback));
    }
    }


    @Test
    public void testTransferStartingWindowFromFinishingActivity() {
        mActivity.addStartingWindow(mPackageName, android.R.style.Theme, null /* compatInfo */,
                "Test", 0 /* labelRes */, 0 /* icon */, 0 /* logo */, 0 /* windowFlags */,
                null /* transferFrom */, true /* newTask */, true /* taskSwitch */,
                false /* processRunning */, false /* allowTaskSnapshot */,
                false /* activityCreate */);
        waitUntilHandlersIdle();
        assertHasStartingWindow(mActivity);
        mActivity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;

        doCallRealMethod().when(mStack).startActivityLocked(
                any(), any(), anyBoolean(), anyBoolean(), any());
        // Make mVisibleSetFromTransferredStartingWindow true.
        final ActivityRecord middle = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
                .setTask(mTask).build();
        mStack.startActivityLocked(middle, null /* focusedTopActivity */,
                false /* newTask */, false /* keepCurTransition */, null /* options */);
        middle.makeFinishingLocked();

        assertNull(mActivity.startingWindow);
        assertHasStartingWindow(middle);

        final ActivityRecord top = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
                .setTask(mTask).build();
        // Expect the visibility should be updated to true when transferring starting window from
        // a visible activity.
        top.setVisible(false);
        // The finishing middle should be able to transfer starting window to top.
        mStack.startActivityLocked(top, null /* focusedTopActivity */,
                false /* newTask */, false /* keepCurTransition */, null /* options */);

        assertNull(middle.startingWindow);
        assertHasStartingWindow(top);
        assertTrue(top.isVisible());
        // The activity was visible by mVisibleSetFromTransferredStartingWindow, so after its
        // starting window is transferred, it should restore to invisible.
        assertFalse(middle.isVisible());
    }

    private ActivityRecord createIsolatedTestActivityRecord() {
    private ActivityRecord createIsolatedTestActivityRecord() {
        final ActivityStack taskStack = createTaskStackOnDisplay(mDisplayContent);
        final ActivityStack taskStack = createTaskStackOnDisplay(mDisplayContent);
        final Task task = createTaskInStack(taskStack, 0 /* userId */);
        final Task task = createTaskInStack(taskStack, 0 /* userId */);