Prevent unnecessary reordering of the home stack
- Just cancel the recents animation in-place when handling the home button
to prevent it from being repositioned to the bottom (which stops home)
and then starting it again (which restarts it)
Bug: 74405472
Test: Press home from an app, ensure launcher stop is only called once
Change-Id: Id41aa2f77c01767cc2c35445458b8f2db81200fc
diff --git a/config/hiddenapi-vendor-list.txt b/config/hiddenapi-vendor-list.txt
index 61ae6e7..3a3a708 100644
--- a/config/hiddenapi-vendor-list.txt
+++ b/config/hiddenapi-vendor-list.txt
@@ -20,7 +20,7 @@
Landroid/app/AppOpsManager$PackageOps;->getPackageName()Ljava/lang/String;
Landroid/app/AppOpsManager$PackageOps;->getUid()I
Landroid/app/IActivityController$Stub;-><init>()V
-Landroid/app/IActivityManager;->cancelRecentsAnimation()V
+Landroid/app/IActivityManager;->cancelRecentsAnimation(Z)V
Landroid/app/IActivityManager;->cancelTaskWindowTransition(I)V
Landroid/app/IActivityManager;->closeSystemDialogs(Ljava/lang/String;)V
Landroid/app/IActivityManager;->getCurrentUser()Landroid/content/pm/UserInfo;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e1a02fa..919f714 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -450,7 +450,7 @@
in Intent intent, in String resolvedType, in Bundle options, int userId);
void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver,
in IRecentsAnimationRunner recentsAnimationRunner);
- void cancelRecentsAnimation();
+ void cancelRecentsAnimation(boolean restoreHomeStackPosition);
int startActivityFromRecents(int taskId, in Bundle options);
Bundle getActivityOptions(in IBinder token);
List<IBinder> getAppTasks(in String callingPackage);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 1aad27f..6aa2754 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -258,9 +258,9 @@
/**
* Cancels the remote recents animation started from {@link #startRecentsActivity}.
*/
- public void cancelRecentsAnimation() {
+ public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
try {
- ActivityManager.getService().cancelRecentsAnimation();
+ ActivityManager.getService().cancelRecentsAnimation(restoreHomeStackPosition);
} catch (RemoteException e) {
Log.e(TAG, "Failed to cancel recents animation", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 5d7e938..e51efa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -52,6 +52,7 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.VibratorHelper;
+import static android.view.KeyEvent.KEYCODE_HOME;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
@@ -270,8 +271,12 @@
if (mCode != 0) {
if (doIt) {
// If there was a pending remote recents animation, then we need to
- // cancel the animation now before we handle the button itself
- ActivityManagerWrapper.getInstance().cancelRecentsAnimation();
+ // cancel the animation now before we handle 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
+ boolean isHomeKey = mCode == KEYCODE_HOME;
+ ActivityManagerWrapper.getInstance().cancelRecentsAnimation(!isHomeKey);
+
sendEvent(KeyEvent.ACTION_UP, 0);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
} else {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 08e5d44..3b22cd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -204,6 +204,8 @@
import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_HOME_IN_PLACE;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -257,7 +259,6 @@
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
import android.app.admin.DevicePolicyCache;
-import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.IBackupManager;
@@ -5225,12 +5226,14 @@
}
@Override
- public void cancelRecentsAnimation() {
+ public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
- mWindowManager.cancelRecentsAnimation();
+ mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition
+ ? REORDER_MOVE_HOME_TO_ORIGINAL_POSITION
+ : REORDER_KEEP_HOME_IN_PLACE);
}
} 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 9df321c..5e74619 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -23,6 +23,8 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_TOP;
import android.app.ActivityOptions;
import android.content.ComponentName;
@@ -32,6 +34,7 @@
import android.os.Trace;
import android.util.Slog;
import android.view.IRecentsAnimationRunner;
+import com.android.server.wm.RecentsAnimationController;
import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
import com.android.server.wm.WindowManagerService;
@@ -123,7 +126,7 @@
// Fetch all the surface controls and pass them to the client to get the animation
// started
- mWindowManager.cancelRecentsAnimation();
+ mWindowManager.cancelRecentsAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this,
display.mDisplayId, mStackSupervisor.mRecentTasks.getRecentTaskIds());
@@ -140,7 +143,7 @@
}
@Override
- public void onAnimationFinished(boolean moveHomeToTop) {
+ public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode) {
synchronized (mService) {
if (mWindowManager.getRecentsAnimationController() == null) return;
@@ -151,9 +154,8 @@
"RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
mWindowManager.deferSurfaceLayout();
try {
- mWindowManager.cleanupRecentsAnimation(moveHomeToTop);
+ mWindowManager.cleanupRecentsAnimation(reorderMode);
- // Move the home stack to the front
final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
if (homeActivity == null) {
return;
@@ -162,15 +164,19 @@
// Restore the launched-behind state
homeActivity.mLaunchTaskBehind = false;
- if (moveHomeToTop) {
+ if (reorderMode == REORDER_MOVE_HOME_TO_TOP) {
// Bring the home stack to the front
final ActivityStack homeStack = homeActivity.getStack();
mStackSupervisor.mNoAnimActivities.add(homeActivity);
homeStack.moveToFront("RecentsAnimation.onAnimationFinished()");
- } else {
+ } else if (reorderMode == REORDER_MOVE_HOME_TO_ORIGINAL_POSITION){
// Restore the home stack to its previous position
final ActivityDisplay display = homeActivity.getDisplay();
display.moveHomeStackBehindStack(mRestoreHomeBehindStack);
+ } else {
+ // Keep home stack in place, nothing changes, so ignore the transition logic
+ // below
+ return;
}
mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index ba67ff6..112d93c 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -161,7 +161,10 @@
// Timeout callback to ensure we continue the animation if waiting for resuming or app
// windows drawn fails
- private final Runnable mResumeRunnable = () -> resume();
+ private final Runnable mResumeRunnable = () -> {
+ if (DEBUG) Slog.d(TAG, "pause: timed out waiting for windows drawn");
+ resume();
+ };
BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
@SchedulePipModeChangedState int schedulePipModeChangedState,
@@ -213,7 +216,7 @@
// When starting an animation from fullscreen, pause here and wait for the
// windows-drawn signal before we start the rest of the transition down into PiP.
- if (mMoveFromFullscreen) {
+ if (mMoveFromFullscreen && mTarget.shouldDeferStartOnMoveToFullscreen()) {
pause();
}
} else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
index 647a2d6..68be4e8 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java
@@ -34,6 +34,12 @@
void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
/**
+ * @return Whether the animation should be paused waiting for the windows to draw before
+ * entering PiP.
+ */
+ boolean shouldDeferStartOnMoveToFullscreen();
+
+ /**
* Sets the size of the target (without any intermediate steps, like scheduling animation)
* but freezes the bounds of any tasks in the target at taskBounds, to allow for more
* flexibility during resizing. Only works for the pinned stack at the moment. This will
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 7274aee..29077f9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import android.annotation.IntDef;
import android.app.ActivityManager.TaskSnapshot;
import android.app.WindowConfiguration;
import android.graphics.Point;
@@ -67,12 +68,25 @@
private static final boolean DEBUG = false;
private static final long FAILSAFE_DELAY = 1000;
+ public static final int REORDER_KEEP_HOME_IN_PLACE = 0;
+ public static final int REORDER_MOVE_HOME_TO_TOP = 1;
+ public static final int REORDER_MOVE_HOME_TO_ORIGINAL_POSITION = 2;
+
+ @IntDef(prefix = { "REORDER_MODE_" }, value = {
+ REORDER_KEEP_HOME_IN_PLACE,
+ REORDER_MOVE_HOME_TO_TOP,
+ REORDER_MOVE_HOME_TO_ORIGINAL_POSITION
+ })
+ public @interface ReorderMode {}
+
private final WindowManagerService mService;
private final IRecentsAnimationRunner mRunner;
private final RecentsAnimationCallbacks mCallbacks;
private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
private final int mDisplayId;
- private final Runnable mFailsafeRunnable = this::cancelAnimation;
+ private final Runnable mFailsafeRunnable = () -> {
+ cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
+ };
// The recents component app token that is shown behind the visibile tasks
private AppWindowToken mHomeAppToken;
@@ -93,7 +107,7 @@
private Rect mTmpRect = new Rect();
public interface RecentsAnimationCallbacks {
- void onAnimationFinished(boolean moveHomeToTop);
+ void onAnimationFinished(@ReorderMode int reorderMode);
}
private final IRecentsAnimationController mController =
@@ -141,7 +155,9 @@
// Note, the callback will handle its own synchronization, do not lock on WM lock
// prior to calling the callback
- mCallbacks.onAnimationFinished(moveHomeToTop);
+ mCallbacks.onAnimationFinished(moveHomeToTop
+ ? REORDER_MOVE_HOME_TO_TOP
+ : REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -222,14 +238,14 @@
// Skip the animation if there is nothing to animate
if (mPendingAnimations.isEmpty()) {
- cancelAnimation();
+ cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
return;
}
try {
mRunner.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
- cancelAnimation();
+ cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
return;
}
@@ -299,7 +315,7 @@
reasons).sendToTarget();
}
- void cancelAnimation() {
+ void cancelAnimation(@ReorderMode int reorderMode) {
if (DEBUG) Log.d(TAG, "cancelAnimation()");
synchronized (mService.getWindowManagerLock()) {
if (mCanceled) {
@@ -316,16 +332,17 @@
}
// Clean up and return to the previous app
// Don't hold the WM lock here as it calls back to AM/RecentsAnimation
- mCallbacks.onAnimationFinished(false /* moveHomeToTop */);
+ mCallbacks.onAnimationFinished(reorderMode);
}
- void cleanupAnimation(boolean moveHomeToTop) {
+ void cleanupAnimation(@ReorderMode int reorderMode) {
if (DEBUG) Log.d(TAG, "cleanupAnimation(): mPendingAnimations="
+ mPendingAnimations.size());
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
adapter.mTask.setCanAffectSystemUiFlags(true);
- if (moveHomeToTop) {
+ if (reorderMode == REORDER_MOVE_HOME_TO_TOP
+ || reorderMode == REORDER_KEEP_HOME_IN_PLACE) {
adapter.mTask.dontAnimateDimExit();
}
adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
@@ -344,7 +361,7 @@
@Override
public void binderDied() {
- cancelAnimation();
+ cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
}
void checkAnimationReady(WallpaperController wallpaperController) {
@@ -458,7 +475,7 @@
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- cancelAnimation();
+ cancelAnimation(REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 62754ad..c97f394 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -33,8 +33,6 @@
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.StackProto.ADJUSTED_BOUNDS;
import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME;
import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT;
@@ -47,6 +45,8 @@
import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT;
import static com.android.server.wm.StackProto.TASKS;
import static com.android.server.wm.StackProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.CallSuper;
import android.content.res.Configuration;
@@ -62,12 +62,10 @@
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
-
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
import com.android.server.EventLogTags;
-
import java.io.PrintWriter;
public class TaskStack extends WindowContainer<Task> implements
@@ -1687,6 +1685,25 @@
}
}
+ @Override
+ public boolean shouldDeferStartOnMoveToFullscreen() {
+ // Workaround for the recents animation -- normally we need to wait for the new activity to
+ // show before starting the PiP animation, but because we start and show the home activity
+ // early for the recents animation prior to the PiP animation starting, there is no
+ // subsequent all-drawn signal. In this case, we can skip the pause when the home stack is
+ // already visible and drawn.
+ final TaskStack homeStack = mDisplayContent.getHomeStack();
+ if (homeStack == null) {
+ return true;
+ }
+ final Task homeTask = homeStack.getTopChild();
+ final AppWindowToken homeApp = homeTask.getTopVisibleAppToken();
+ if (!homeTask.isVisible() || homeApp == null) {
+ return true;
+ }
+ return !homeApp.allDrawn;
+ }
+
/**
* @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen
* bounds and we have a deferred PiP mode changed callback set with the animation.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index c509980..6de1579 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -26,6 +26,7 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_HOME_TO_ORIGINAL_POSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
@@ -618,7 +619,8 @@
// If there was a recents animation in progress, cancel that animation
if (mService.getRecentsAnimationController() != null) {
- mService.getRecentsAnimationController().cancelAnimation();
+ mService.getRecentsAnimationController().cancelAnimation(
+ REORDER_MOVE_HOME_TO_ORIGINAL_POSITION);
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f1cd46b..28a57f5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2687,20 +2687,20 @@
}
}
- public void cancelRecentsAnimation() {
+ public void cancelRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
// Note: Do not hold the WM lock, this will lock appropriately in the call which also
// calls through to AM/RecentsAnimation.onAnimationFinished()
if (mRecentsAnimationController != null) {
// This call will call through to cleanupAnimation() below after the animation is
// canceled
- mRecentsAnimationController.cancelAnimation();
+ mRecentsAnimationController.cancelAnimation(reorderMode);
}
}
- public void cleanupRecentsAnimation(boolean moveHomeToTop) {
+ public void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
synchronized (mWindowMap) {
if (mRecentsAnimationController != null) {
- mRecentsAnimationController.cleanupAnimation(moveHomeToTop);
+ mRecentsAnimationController.cleanupAnimation(reorderMode);
mRecentsAnimationController = null;
mAppTransition.updateBooster();
}
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index 7765387..6290ff1 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -649,7 +649,7 @@
assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
null));
- assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation());
+ assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true));
}
private void testGetTasksApis(boolean expectCallable) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 2bfe274..6019958 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -159,6 +159,11 @@
}
@Override
+ public boolean shouldDeferStartOnMoveToFullscreen() {
+ return true;
+ }
+
+ @Override
public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
// TODO: Once we break the runs apart, we should fail() here if this is called outside
// of onAnimationStart() and onAnimationEnd()