diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index 0f42103e41782..27eae57302b0e 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -78,9 +78,13 @@ class ActivityDisplay extends ConfigurationContainer int mDisplayId; Display mDisplay; - /** All of the stacks on this display. Order matters, topmost stack is in front of all other - * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ + /** + * All of the stacks on this display. Order matters, topmost stack is in front of all other + * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls + * changing the list should also call {@link #onStackOrderChanged()}. + */ private final ArrayList mStacks = new ArrayList<>(); + private ArrayList mStackOrderChangedCallbacks = new ArrayList<>(); /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -145,6 +149,7 @@ class ActivityDisplay extends ConfigurationContainer mStacks.remove(stack); removeStackReferenceIfNeeded(stack); mSupervisor.mService.updateSleepIfNeededLocked(); + onStackOrderChanged(); } void positionChildAtTop(ActivityStack stack) { @@ -163,6 +168,7 @@ class ActivityDisplay extends ConfigurationContainer mStacks.add(insertPosition, stack); mWindowContainerController.positionChildAt(stack.getWindowContainerController(), insertPosition); + onStackOrderChanged(); } private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { @@ -770,6 +776,30 @@ class ActivityDisplay extends ConfigurationContainer mSleeping = asleep; } + /** + * Adds a listener to be notified whenever the stack order in the display changes. Currently + * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the + * current animation when the system state changes. + */ + void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { + if (!mStackOrderChangedCallbacks.contains(listener)) { + mStackOrderChangedCallbacks.add(listener); + } + } + + /** + * Removes a previously registered stack order change listener. + */ + void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { + mStackOrderChangedCallbacks.remove(listener); + } + + private void onStackOrderChanged() { + for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { + mStackOrderChangedCallbacks.get(i).onStackOrderChanged(); + } + } + public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size()); final String myPrefix = prefix + " "; @@ -806,4 +836,11 @@ class ActivityDisplay extends ConfigurationContainer } proto.end(token); } + + /** + * Callback for when the order of the stacks in the display changes. + */ + interface OnStackOrderChangedListener { + void onStackOrderChanged(); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f25f50646617e..cf50b1a1d6e79 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5291,14 +5291,9 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingPid = Binder.getCallingPid(); final long origId = Binder.clearCallingIdentity(); try { - final int recentsUid; - final String recentsPackage; - final List topVisibleActivities; synchronized (this) { final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); - recentsPackage = recentsComponent.getPackageName(); - recentsUid = mRecentTasks.getRecentsComponentUid(); - topVisibleActivities = mStackSupervisor.getTopVisibleActivities(); + final int recentsUid = mRecentTasks.getRecentsComponentUid(); // Start a new recents animation final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor, @@ -5314,13 +5309,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void cancelRecentsAnimation(boolean restoreHomeStackPosition) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()"); + final long callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { synchronized (this) { // Cancel the recents animation synchronously (do not hold the WM lock) mWindowManager.cancelRecentsAnimationSynchronously(restoreHomeStackPosition ? REORDER_MOVE_TO_ORIGINAL_POSITION - : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation"); + : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation/uid=" + callingUid); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index a88f4080a784c..a3d2173b294cb 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -49,7 +49,8 @@ import com.android.server.wm.WindowManagerService; * Manages the recents animation, including the reordering of the stacks for the transition and * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. */ -class RecentsAnimation implements RecentsAnimationCallbacks { +class RecentsAnimation implements RecentsAnimationCallbacks, + ActivityDisplay.OnStackOrderChangedListener { private static final String TAG = RecentsAnimation.class.getSimpleName(); // TODO (b/73188263): Reset debugging flags private static final boolean DEBUG = true; @@ -140,13 +141,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks { recentsUid, recentsComponent.getPackageName()); } - final ActivityDisplay display; if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most - display = targetActivity.getDisplay(); - display.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack); if (DEBUG) Slog.d(TAG, "Moved stack=" + targetStack + " behind stack=" - + display.getStackAbove(targetStack)); + + mDefaultDisplay.getStackAbove(targetStack)); // If there are multiple tasks in the target stack (ie. the home stack, with 3p // and default launchers coexisting), then move the task to the top as a part of @@ -173,7 +172,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { targetActivity = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType).getTopActivity(); - display = targetActivity.getDisplay(); // TODO: Maybe wait for app to draw in this particular case? @@ -190,7 +188,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, - this, display.mDisplayId, mStackSupervisor.mRecentTasks.getRecentTaskIds()); + this, mDefaultDisplay.mDisplayId, + mStackSupervisor.mRecentTasks.getRecentTaskIds()); // If we updated the launch-behind state, update the visibility of the activities after // we fetch the visible tasks to be controlled by the animation @@ -198,6 +197,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT, targetActivity); + + // Register for stack order changes + mDefaultDisplay.registerStackOrderChangedListener(this); } catch (Exception e) { Slog.e(TAG, "Failed to start recents activity", e); throw e; @@ -219,6 +221,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mAssistDataRequester = null; } + // Unregister for stack order changes + mDefaultDisplay.unregisterStackOrderChangedListener(this); + if (mWindowManager.getRecentsAnimationController() == null) return; // Just to be sure end the launch hint in case the target activity was never launched. @@ -316,6 +321,14 @@ class RecentsAnimation implements RecentsAnimationCallbacks { } } + @Override + public void onStackOrderChanged() { + // If the activity display stack order changes, cancel any running recents animation in + // place + mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE, + "stackOrderChanged"); + } + /** * Called only when the animation should be canceled prior to starting. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 18f3434ece490..e6d851b83aa8b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -6092,14 +6092,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (!isNavBarVirtKey || mNavBarVirtualKeyHapticFeedbackEnabled) && event.getRepeatCount() == 0; - // Cancel any pending remote recents animations before handling the button itself. In the - // case where we are going home and the recents animation has already started, just cancel - // the recents animation, leaving the home stack in place for the pending start activity - if (isNavBarVirtKey && !down && !canceled) { - boolean isHomeKey = keyCode == KeyEvent.KEYCODE_HOME; - mActivityManagerInternal.cancelRecentsAnimation(!isHomeKey); - } - // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { 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 4b8dcc1e56c1a..01425ed51b559 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 @@ public class ActivityStackTests extends ActivityTestsBase { 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 @@ public class ActivityStackTests extends ActivityTestsBase { 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 0000000000000..eefd973112f5a --- /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; + } + } +}