Cancel any recents animation whenever a display's stack order changes
- The recents animation holds a reference to the stack that the recents
stack was previously behind (before the animation started) and should be
restored behind after the animation ends. Due to the ordering of events,
it is possible for a call to cancelRecentsAnimation() to come in after
we have started the transition to home
ie.
1) in app A
2) touch down on home button -> start recents animation, home stack
saves A stack as the one to restore behind if animation doesn't
finish
3) touch up to trigger button -> PWM to start home activity and
simultaneously proxies the motion even to launcher which happens to
call cancelRecentsAnimation (as a part of a catch all)
4) PWM start activity -> move home stack to top
5) cancelRecentsAnimation from launcher will try to restore the home
stack behind A, when the rest of the system has already thought
that the home stack was already moved to the front and focused
Instead of depending on any particular ordering of events, we can just
cancel the recents animation (without reordering) whenever the display's
stacks change so as not to affect any upcoming changes. There is only
one stack order changed listener (the recents animation, and there can
only be one animation at a time; the previous animation is always
canceled and unregisters the listener when cleaning up.
- Revert the old workaround for Beta 1 in PWM as it doesn't catch other
cases
- Removed confusing code suggesting that we were using a non-default
display (we are always only ever doing this animation for the default
display since that's where we resolved the target stack).
Bug: 73188263
Test: Was able to repro by artificially calling cancelRecentsAnimation
immediately after starting home (and also by swiping up, holding,
then invoking the home via adb (to trigger start activity) and then
releasing (to trigger cancelRecentsAnimation)). Ensure that case
now works.
Test: atest FrameworksServicesTests:RecentsAnimationTest
Test: atest FrameworksServicesTests:ActivityStackTests
Change-Id: Iec1a29b5bb53737b4be3f1a3719c4e7c26313a11
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 4b8dcc1..01425ed 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -565,6 +565,47 @@
false /* displaySleeping */, false /* expected*/);
}
+ @Test
+ public void testStackOrderChangedOnRemoveStack() throws Exception {
+ StackOrderChangedListener listener = new StackOrderChangedListener();
+ mDefaultDisplay.registerStackOrderChangedListener(listener);
+ try {
+ mDefaultDisplay.removeChild(mStack);
+ } finally {
+ mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+ }
+ assertTrue(listener.changed);
+ }
+
+ @Test
+ public void testStackOrderChangedOnAddPositionStack() throws Exception {
+ mDefaultDisplay.removeChild(mStack);
+
+ StackOrderChangedListener listener = new StackOrderChangedListener();
+ mDefaultDisplay.registerStackOrderChangedListener(listener);
+ try {
+ mDefaultDisplay.addChild(mStack, 0);
+ } finally {
+ mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+ }
+ assertTrue(listener.changed);
+ }
+
+ @Test
+ public void testStackOrderChangedOnPositionStack() throws Exception {
+ StackOrderChangedListener listener = new StackOrderChangedListener();
+ try {
+ final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest(
+ mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ mDefaultDisplay.registerStackOrderChangedListener(listener);
+ mDefaultDisplay.positionChildAtBottom(fullscreenStack1);
+ } finally {
+ mDefaultDisplay.unregisterStackOrderChangedListener(listener);
+ }
+ assertTrue(listener.changed);
+ }
+
private void verifyShouldSleepActivities(boolean focusedStack,
boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
mSupervisor.mFocusedStack = focusedStack ? mStack : null;
@@ -578,4 +619,13 @@
assertEquals(expected, mStack.shouldSleepActivities());
}
+
+ private class StackOrderChangedListener implements ActivityDisplay.OnStackOrderChangedListener {
+ boolean changed = false;
+
+ @Override
+ public void onStackOrderChanged() {
+ changed = true;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java
new file mode 100644
index 0000000..eefd973
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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 android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.IRecentsAnimationRunner;
+import com.android.server.AttributeCache;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * atest FrameworksServicesTests:RecentsAnimationTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RecentsAnimationTest extends ActivityTestsBase {
+ private static final int TEST_CALLING_PID = 3;
+
+ private Context mContext = InstrumentationRegistry.getContext();
+ private ActivityManagerService mService;
+ private ComponentName mRecentsComponent;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity");
+ mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+ AttributeCache.init(mContext);
+ }
+
+ @Test
+ public void testCancelAnimationOnStackOrderChange() throws Exception {
+ ActivityStack fullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ ActivityStack recentsStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ ActivityRecord recentsActivity = new ActivityBuilder(mService)
+ .setComponent(mRecentsComponent)
+ .setCreateTask(true)
+ .setStack(recentsStack)
+ .build();
+ ActivityStack fullscreenStack2 = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ ActivityRecord fsActivity = new ActivityBuilder(mService)
+ .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+ .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));
+
+ fullscreenStack.moveToFront("Activity start");
+
+ // Ensure that the recents animation was canceled
+ verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
+ eq(REORDER_KEEP_IN_PLACE), any());
+ }
+
+ private class MyTestActivityManagerService extends TestActivityManagerService {
+ MyTestActivityManagerService(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected RecentTasks createRecentTasks() {
+ RecentTasks recents = mock(RecentTasks.class);
+ doReturn(mRecentsComponent).when(recents).getRecentsComponent();
+ System.out.println(mRecentsComponent);
+ return recents;
+ }
+ }
+}