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

Commit 43233b72 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Fix mLaunchTaskBehind is not restored when canceling recents

If a 3rd party launcher is set as default home, when pressing
home button to go back from app, the launch-behind of recents
activity will be set. And then 3rd party launcher will move
to top that triggers stack order changed to cancel the recents.
That includes to restore launch-behind but the current top is
not the original one that was set. So there are 2 activities
show at the same time.

With storing the original target activity, we are able to
restore the launch-behind state correctly.

Bug: 131589476
Test: atest RecentsAnimationTest#testSetLaunchTaskBehindOfTargetActivity
Change-Id: Ib923492bb6263482fa22bd031a716b48ded20f91
parent b037feb1
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -60,6 +60,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
    private final ActivityDisplay mDefaultDisplay;
    private final int mCallingPid;

    /**
     * The activity which has been launched behind. We need to remember the activity because the
     * target stack may have other activities, then we are able to restore the launch-behind state
     * for the exact activity.
     */
    private ActivityRecord mLaunchedTargetActivity;
    private int mTargetActivityType;

    // The stack to restore the target stack behind when the animation is finished
@@ -175,6 +181,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
            // Mark the target activity as launch-behind to bump its visibility for the
            // duration of the gesture that is driven by the recents component
            targetActivity.mLaunchTaskBehind = true;
            mLaunchedTargetActivity = targetActivity;

            // Fetch all the surface controls and pass them to the client to get the animation
            // started. Cancel any existing recents animation running synchronously (do not hold the
@@ -233,8 +240,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks,

                    final ActivityStack targetStack = mDefaultDisplay.getStack(
                            WINDOWING_MODE_UNDEFINED, mTargetActivityType);
                    // Prefer to use the original target activity instead of top activity because
                    // we may have moved another task to top (starting 3p launcher).
                    final ActivityRecord targetActivity = targetStack != null
                            ? targetStack.getTopActivity()
                            ? targetStack.isInStackLocked(mLaunchedTargetActivity)
                            : null;
                    if (DEBUG) Slog.d(TAG, "onAnimationFinished(): targetStack=" + targetStack
                            + " targetActivity=" + targetActivity
+4 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM
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.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
@@ -132,6 +133,9 @@ class ActivityTestsBase {
            mService.setWindowManager(null);
            mService = null;
        }
        if (sMockWindowManagerService != null) {
            reset(sMockWindowManagerService);
        }

        mMockTracker.close();
        mMockTracker = null;
+79 −15
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -28,17 +29,20 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.platform.test.annotations.Presubmit;
import android.view.IRecentsAnimationRunner;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;

import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;

import org.junit.Before;
import org.junit.Test;

@@ -50,24 +54,62 @@ import org.junit.Test;
@Presubmit
public class RecentsAnimationTest extends ActivityTestsBase {

    private Context mContext = InstrumentationRegistry.getContext();
    private ComponentName mRecentsComponent;
    private final ComponentName mRecentsComponent =
            new ComponentName(mContext.getPackageName(), "RecentsActivity");
    private RecentsAnimationController mRecentsAnimationController;

    @Before
    public void setUp() throws Exception {
        mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity");
        mService = new TestActivityTaskManagerService(mContext);
        mRecentsAnimationController = mock(RecentsAnimationController.class);
        doReturn(mRecentsAnimationController).when(
                mService.mWindowManager).getRecentsAnimationController();
        doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();

        final RecentTasks recentTasks = mService.getRecentTasks();
        spyOn(recentTasks);
        mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity");
        doReturn(mRecentsComponent).when(recentTasks).getRecentsComponent();
    }

    @Test
    public void testSetLaunchTaskBehindOfTargetActivity() {
        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
        display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
        ActivityStack homeStack = display.getHomeStack();
        // Assume the home activity support recents.
        ActivityRecord targetActivity = homeStack.getTopActivity();
        // Put another home activity in home stack.
        ActivityRecord anotherHomeActivity = new ActivityBuilder(mService)
                .setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
                .setCreateTask(true)
                .setStack(homeStack)
                .build();
        // Start an activity on top so the recents activity can be started.
        new ActivityBuilder(mService)
                .setCreateTask(true)
                .build()
                .getActivityStack()
                .moveToFront("Activity start");

        // Start the recents animation.
        RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                targetActivity.getTaskRecord().getBaseIntent().getComponent(),
                true /* getRecentsAnimation */);
        // Ensure launch-behind is set for being visible.
        assertTrue(targetActivity.mLaunchTaskBehind);

        anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
        // The current top activity is not the recents so the animation should be canceled.
        verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
                eq(REORDER_KEEP_IN_PLACE), any() /* reason */);

        // The test uses mocked RecentsAnimationController so we have to invoke the callback
        // manually to simulate the flow.
        recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, true /* runSychronously */,
                false /* sendUserLeaveHint */);
        // We should restore the launch-behind of the original target activity.
        assertFalse(targetActivity.mLaunchTaskBehind);
    }

    @Test
    public void testCancelAnimationOnVisibleStackOrderChange() {
        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
@@ -93,12 +135,9 @@ public class RecentsAnimationTest extends ActivityTestsBase {
                .setCreateTask(true)
                .setStack(fullscreenStack2)
                .build();
        doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();

        // Start the recents animation
        Intent recentsIntent = new Intent();
        recentsIntent.setComponent(mRecentsComponent);
        mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class));
        startRecentsActivity();

        fullscreenStack.moveToFront("Activity start");

@@ -140,12 +179,9 @@ public class RecentsAnimationTest extends ActivityTestsBase {
                .setCreateTask(true)
                .setStack(fullscreenStack2)
                .build();
        doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();

        // Start the recents animation
        Intent recentsIntent = new Intent();
        recentsIntent.setComponent(mRecentsComponent);
        mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class));
        startRecentsActivity();

        fullscreenStack.remove();

@@ -154,4 +190,32 @@ public class RecentsAnimationTest extends ActivityTestsBase {
                eq(REORDER_KEEP_IN_PLACE), any());
        verify(mRecentsAnimationController, times(0)).cancelOnNextTransitionStart();
    }

    private void startRecentsActivity() {
        startRecentsActivity(mRecentsComponent, false /* getRecentsAnimation */);
    }

    /**
     * @return non-null {@link RecentsAnimationCallbacks} if the given {@code getRecentsAnimation}
     *         is {@code true}.
     */
    private RecentsAnimationCallbacks startRecentsActivity(ComponentName recentsComponent,
            boolean getRecentsAnimation) {
        RecentsAnimationCallbacks[] recentsAnimation = { null };
        if (getRecentsAnimation) {
            doAnswer(invocation -> {
                // The callback is actually RecentsAnimation.
                recentsAnimation[0] = invocation.getArgument(2);
                return null;
            }).when(mService.mWindowManager).initializeRecentsAnimation(
                    anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */,
                    any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */);
        }

        Intent recentsIntent = new Intent();
        recentsIntent.setComponent(recentsComponent);
        mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
                mock(IRecentsAnimationRunner.class));
        return recentsAnimation[0];
    }
}