From e2d721781fc024cbd9a14929741e5b476242291f Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 25 Jan 2018 17:46:20 +0000 Subject: [PATCH] Revert "Revert "2/ Add support for remote Recents animation"" This reverts commit 9f8518e532e41ba57916afc49bba72bc23ad3eda. Reason for revert: Testing relanding changes with ag/3515280 Change-Id: I410bd752c815a5b998a719453def01e00a9d47c8 --- Android.bp | 2 + core/java/android/app/ActivityOptions.java | 28 ++ core/java/android/app/IActivityManager.aidl | 6 +- .../view/IRecentsAnimationController.aidl | 54 +++ .../android/view/IRecentsAnimationRunner.aidl | 42 ++ .../android/view/RemoteAnimationTarget.java | 12 +- .../android/server/am/ActivityDisplay.java | 59 +++ .../server/am/ActivityManagerService.java | 51 +-- .../server/am/ActivityStackSupervisor.java | 2 + .../android/server/am/ActivityStarter.java | 31 +- .../android/server/am/RecentsAnimation.java | 159 ++++++++ .../com/android/server/wm/DisplayContent.java | 14 + .../server/wm/RecentsAnimationController.java | 373 ++++++++++++++++++ .../server/wm/RemoteAnimationController.java | 3 +- .../server/wm/RootWindowContainer.java | 7 + .../android/server/wm/SurfaceAnimator.java | 9 +- .../server/wm/WallpaperController.java | 31 +- .../android/server/wm/WindowContainer.java | 4 +- .../server/wm/WindowManagerService.java | 42 ++ .../android/server/am/RecentTasksTest.java | 3 +- 20 files changed, 881 insertions(+), 51 deletions(-) create mode 100644 core/java/android/view/IRecentsAnimationController.aidl create mode 100644 core/java/android/view/IRecentsAnimationRunner.aidl create mode 100644 services/core/java/com/android/server/am/RecentsAnimation.java create mode 100644 services/core/java/com/android/server/wm/RecentsAnimationController.java diff --git a/Android.bp b/Android.bp index ee2281fe27e5..d8bcdbbbe26f 100644 --- a/Android.bp +++ b/Android.bp @@ -331,6 +331,8 @@ java_library { "core/java/android/view/IPinnedStackController.aidl", "core/java/android/view/IPinnedStackListener.aidl", "core/java/android/view/IRemoteAnimationRunner.aidl", + "core/java/android/view/IRecentsAnimationController.aidl", + "core/java/android/view/IRecentsAnimationRunner.aidl", "core/java/android/view/IRemoteAnimationFinishedCallback.aidl", "core/java/android/view/IRotationWatcher.aidl", "core/java/android/view/IWallpaperVisibilityListener.aidl", diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4bcd677e1f4e..fee58274a5fc 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -206,6 +206,12 @@ public class ActivityOptions { private static final String KEY_TASK_OVERLAY_CAN_RESUME = "android.activity.taskOverlayCanResume"; + /** + * See {@link #setAvoidMoveToFront()}. + * @hide + */ + private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront"; + /** * Where the split-screen-primary stack should be positioned. * @hide @@ -307,6 +313,7 @@ public class ActivityOptions { private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; + private boolean mAvoidMoveToFront; private AppTransitionAnimationSpec mAnimSpecs[]; private int mRotationAnimationHint = -1; private Bundle mAppVerificationBundle; @@ -923,6 +930,7 @@ public class ActivityOptions { mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); + mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false); mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( @@ -1239,6 +1247,25 @@ public class ActivityOptions { return mTaskOverlayCanResume; } + /** + * Sets whether the activity launched should not cause the activity stack it is contained in to + * be moved to the front as a part of launching. + * + * @hide + */ + public void setAvoidMoveToFront() { + mAvoidMoveToFront = true; + } + + /** + * @return whether the activity launch should prevent moving the associated activity stack to + * the front. + * @hide + */ + public boolean getAvoidMoveToFront() { + return mAvoidMoveToFront; + } + /** @hide */ public int getSplitScreenCreateMode() { return mSplitScreenCreateMode; @@ -1416,6 +1443,7 @@ public class ActivityOptions { b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume); + b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront); b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, mDisallowEnterPictureInPictureWhileLaunching); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 5382e6656bf1..939762ac4a30 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -66,6 +66,7 @@ import android.os.PersistableBundle; import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; +import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationDefinition; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -442,8 +443,9 @@ interface IActivityManager { in Bundle options, int userId); int startAssistantActivity(in String callingPackage, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); - int startRecentsActivity(in IAssistDataReceiver assistDataReceiver, in Bundle options, - in Bundle activityOptions, int userId); + void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver, + in IRecentsAnimationRunner recentsAnimationRunner); + void cancelRecentsAnimation(); int startActivityFromRecents(int taskId, in Bundle options); Bundle getActivityOptions(in IBinder token); List getAppTasks(in String callingPackage); diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl new file mode 100644 index 000000000000..5607b1134e5b --- /dev/null +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 android.view; + +import android.app.ActivityManager; +import android.view.IRemoteAnimationFinishedCallback; +import android.graphics.GraphicBuffer; + +/** + * Passed to the {@link IRecentsAnimationRunner} in order for the runner to control to let the + * runner control certain aspects of the recents animation, and to notify window manager when the + * animation has completed. + * + * {@hide} + */ +interface IRecentsAnimationController { + + /** + * Takes a screenshot of the task associated with the given {@param taskId}. Only valid for the + * current set of task ids provided to the handler. + */ + ActivityManager.TaskSnapshot screenshotTask(int taskId); + + /** + * Notifies to the system that the animation into Recents should end, and all leashes associated + * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then + * the home activity should be moved to the top. Otherwise, the home activity is hidden and the + * user is returned to the app. + */ + void finish(boolean moveHomeToTop); + + /** + * Called by the handler to indicate that the recents animation input consumer should be + * enabled. This is currently used to work around an issue where registering an input consumer + * mid-animation causes the existing motion event chain to be canceled. Instead, the caller + * may register the recents animation input consumer prior to starting the recents animation + * and then enable it mid-animation to start receiving touch events. + */ + void setInputConsumerEnabled(boolean enabled); +} diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl new file mode 100644 index 000000000000..ea6226b3ea69 --- /dev/null +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 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 android.view; + +import android.view.RemoteAnimationTarget; +import android.view.IRecentsAnimationController; + +/** + * Interface that is used to callback from window manager to the process that runs a recents + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IRecentsAnimationRunner { + + /** + * Called when the system is ready for the handler to start animating all the visible tasks. + */ + void onAnimationStart(in IRecentsAnimationController controller, + in RemoteAnimationTarget[] apps); + + /** + * Called when the system needs to cancel the current animation. This can be due to the + * wallpaper not drawing in time, or the handler not finishing the animation within a predefined + * amount of time. + */ + void onAnimationCanceled(); +} diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index f39e618e169d..c28c3894482d 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.app.WindowConfiguration; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; @@ -98,8 +99,14 @@ public class RemoteAnimationTarget implements Parcelable { */ public final Rect sourceContainerBounds; + /** + * The window configuration for the target. + */ + public final WindowConfiguration windowConfiguration; + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, - Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds) { + Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds, + WindowConfiguration windowConfig) { this.mode = mode; this.taskId = taskId; this.leash = leash; @@ -108,6 +115,7 @@ public class RemoteAnimationTarget implements Parcelable { this.prefixOrderIndex = prefixOrderIndex; this.position = new Point(position); this.sourceContainerBounds = new Rect(sourceContainerBounds); + this.windowConfiguration = windowConfig; } public RemoteAnimationTarget(Parcel in) { @@ -119,6 +127,7 @@ public class RemoteAnimationTarget implements Parcelable { prefixOrderIndex = in.readInt(); position = in.readParcelable(null); sourceContainerBounds = in.readParcelable(null); + windowConfiguration = in.readParcelable(null); } @Override @@ -136,6 +145,7 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeInt(prefixOrderIndex); dest.writeParcelable(position, 0 /* flags */); dest.writeParcelable(sourceContainerBounds, 0 /* flags */); + dest.writeParcelable(windowConfiguration, 0 /* flags */); } public static final Creator CREATOR diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index 46ab0e09436a..aa8d56b3f6c6 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -664,6 +665,64 @@ class ActivityDisplay extends ConfigurationContainer && (mSupervisor.mService.mRunningVoice == null); } + /** + * @return the stack currently above the home stack. Can be null if there is no home stack, or + * the home stack is already on top. + */ + ActivityStack getStackAboveHome() { + if (mHomeStack == null) { + // Skip if there is no home stack + return null; + } + + final int stackIndex = mStacks.indexOf(mHomeStack) + 1; + return (stackIndex < mStacks.size()) ? mStacks.get(stackIndex) : null; + } + + /** + * Adjusts the home stack behind the last visible stack in the display if necessary. Generally + * used in conjunction with {@link #moveHomeStackBehindStack}. + */ + void moveHomeStackBehindBottomMostVisibleStack() { + if (mHomeStack == null) { + // Skip if there is no home stack + return; + } + + // Move the home stack to the bottom to not affect the following visibility checks + positionChildAtBottom(mHomeStack); + + // Find the next position where the homes stack should be placed + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { + final ActivityStack stack = mStacks.get(stackNdx); + if (stack == mHomeStack) { + continue; + } + final int winMode = stack.getWindowingMode(); + final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN || + winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + if (stack.shouldBeVisible(null) && isValidWindowingMode) { + // Move the home stack to behind this stack + positionChildAt(mHomeStack, Math.max(0, stackNdx - 1)); + break; + } + } + } + + /** + * Moves the home stack behind the given {@param stack} if possible. If {@param stack} is not + * currently in the display, then then the home stack is moved to the back. Generally used in + * conjunction with {@link #moveHomeStackBehindBottomMostVisibleStack}. + */ + void moveHomeStackBehindStack(ActivityStack behindStack) { + if (behindStack == null) { + return; + } + + positionChildAt(mHomeStack, Math.max(0, mStacks.indexOf(behindStack) - 1)); + } + boolean isSleeping() { return mSleeping; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f469437801d3..f4bf88438499 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -39,6 +39,7 @@ import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE; import static android.app.AppOpsManager.OP_NONE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -377,6 +378,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; import android.view.Gravity; +import android.view.IRecentsAnimationRunner; import android.view.LayoutInflater; import android.view.RemoteAnimationDefinition; import android.view.View; @@ -450,6 +452,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.utils.PriorityDump; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.PinnedStackWindowController; +import com.android.server.wm.RecentsAnimationController; import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; @@ -5096,23 +5099,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle options, - Bundle activityOptions, int userId) { - if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) { - String msg = "Permission Denial: startRecentsActivity() from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " not recent tasks package"; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options); - final int recentsUid = mRecentTasks.getRecentsComponentUid(); - final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); - final String recentsPackage = recentsComponent.getPackageName(); + public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver, + IRecentsAnimationRunner recentsAnimationRunner) { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()"); final long origId = Binder.clearCallingIdentity(); try { synchronized (this) { + final int recentsUid = mRecentTasks.getRecentsComponentUid(); + final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); + final String recentsPackage = recentsComponent.getPackageName(); + // If provided, kick off the request for the assist data in the background before // starting the activity if (assistDataReceiver != null) { @@ -5129,17 +5125,24 @@ public class ActivityManagerService extends IActivityManager.Stub recentsUid, recentsPackage); } - final Intent intent = new Intent(); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - intent.setComponent(recentsComponent); - intent.putExtras(options); + // Start a new recents animation + final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor, + mActivityStartController, mWindowManager, mUserController); + anim.startRecentsActivity(intent, recentsAnimationRunner, recentsComponent, + recentsUid); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } - return mActivityStartController.obtainStarter(intent, "startRecentsActivity") - .setCallingUid(recentsUid) - .setCallingPackage(recentsPackage) - .setActivityOptions(safeOptions) - .setMayWait(userId) - .execute(); + @Override + public void cancelRecentsAnimation() { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + mWindowManager.cancelRecentsAnimation(); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index bfb563fd93a8..510a3fa47ec5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -297,6 +297,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private RunningTasks mRunningTasks; final ActivityStackSupervisorHandler mHandler; + final Looper mLooper; /** Short cut */ WindowManagerService mWindowManager; @@ -581,6 +582,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D public ActivityStackSupervisor(ActivityManagerService service, Looper looper) { mService = service; + mLooper = looper; mHandler = new ActivityStackSupervisorHandler(looper); } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 4dc30ddf4b5b..8fd754af1a0f 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -302,6 +302,7 @@ class ActivityStarter { SafeActivityOptions activityOptions; boolean ignoreTargetSecurity; boolean componentSpecified; + boolean avoidMoveToFront; ActivityRecord[] outActivity; TaskRecord inTask; String reason; @@ -356,6 +357,7 @@ class ActivityStarter { userId = 0; waitResult = null; mayWait = false; + avoidMoveToFront = false; } /** @@ -390,6 +392,7 @@ class ActivityStarter { userId = request.userId; waitResult = request.waitResult; mayWait = request.mayWait; + avoidMoveToFront = request.avoidMoveToFront; } } @@ -1485,19 +1488,23 @@ class ActivityStarter { mDoResume = false; } - if (mOptions != null && mOptions.getLaunchTaskId() != -1 - && mOptions.getTaskOverlay()) { - r.mTaskOverlay = true; - if (!mOptions.canTaskOverlayResume()) { - final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId()); - final ActivityRecord top = task != null ? task.getTopActivity() : null; - if (top != null && top.state != RESUMED) { + if (mOptions != null) { + if (mOptions.getLaunchTaskId() != -1 && mOptions.getTaskOverlay()) { + r.mTaskOverlay = true; + if (!mOptions.canTaskOverlayResume()) { + final TaskRecord task = mSupervisor.anyTaskForIdLocked( + mOptions.getLaunchTaskId()); + final ActivityRecord top = task != null ? task.getTopActivity() : null; + if (top != null && top.state != RESUMED) { - // The caller specifies that we'd like to be avoided to be moved to the front, - // so be it! - mDoResume = false; - mAvoidMoveToFront = true; + // The caller specifies that we'd like to be avoided to be moved to the + // front, so be it! + mDoResume = false; + mAvoidMoveToFront = true; + } } + } else if (mOptions.getAvoidMoveToFront()) { + mAvoidMoveToFront = true; } } @@ -1838,7 +1845,7 @@ class ActivityStarter { // Need to update mTargetStack because if task was moved out of it, the original stack may // be destroyed. mTargetStack = intentActivity.getStack(); - if (!mMovedToFront && mDoResume) { + if (!mAvoidMoveToFront && !mMovedToFront && mDoResume) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack + " from " + intentActivity); mTargetStack.moveToFront("intentActivityFound"); diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java new file mode 100644 index 000000000000..fe576fdaacbe --- /dev/null +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 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.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_NONE; +import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; + +import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Handler; +import android.view.IRecentsAnimationRunner; +import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks; +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 { + private static final String TAG = RecentsAnimation.class.getSimpleName(); + + private static final int RECENTS_ANIMATION_TIMEOUT = 10 * 1000; + + private final ActivityManagerService mService; + private final ActivityStackSupervisor mStackSupervisor; + private final ActivityStartController mActivityStartController; + private final WindowManagerService mWindowManager; + private final UserController mUserController; + private final Handler mHandler; + + private final Runnable mCancelAnimationRunnable; + + // The stack to restore the home stack behind when the animation is finished + private ActivityStack mRestoreHomeBehindStack; + + RecentsAnimation(ActivityManagerService am, ActivityStackSupervisor stackSupervisor, + ActivityStartController activityStartController, WindowManagerService wm, + UserController userController) { + mService = am; + mStackSupervisor = stackSupervisor; + mActivityStartController = activityStartController; + mHandler = new Handler(mStackSupervisor.mLooper); + mWindowManager = wm; + mUserController = userController; + mCancelAnimationRunnable = () -> { + // The caller has not finished the animation in a predefined amount of time, so + // force-cancel the animation + mWindowManager.cancelRecentsAnimation(); + }; + } + + void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner, + ComponentName recentsComponent, int recentsUid) { + + // Cancel the previous recents animation if necessary + mWindowManager.cancelRecentsAnimation(); + + final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null; + if (!hasExistingHomeActivity) { + // No home activity + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setLaunchActivityType(ACTIVITY_TYPE_HOME); + opts.setAvoidMoveToFront(); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION); + + mActivityStartController.obtainStarter(intent, "startRecentsActivity_noHomeActivity") + .setCallingUid(recentsUid) + .setCallingPackage(recentsComponent.getPackageName()) + .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle())) + .setMayWait(mUserController.getCurrentUserId()) + .execute(); + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + + // TODO: Maybe wait for app to draw in this particular case? + } + + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + final ActivityDisplay display = homeActivity.getDisplay(); + + // Save the initial position of the home activity stack to be restored to after the + // animation completes + mRestoreHomeBehindStack = hasExistingHomeActivity + ? display.getStackAboveHome() + : null; + + // Move the home activity into place for the animation + display.moveHomeStackBehindBottomMostVisibleStack(); + + // Mark the home activity as launch-behind to bump its visibility for the + // duration of the gesture that is driven by the recents component + homeActivity.mLaunchTaskBehind = true; + + // Fetch all the surface controls and pass them to the client to get the animation + // started + mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId); + + // 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 + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); + + // Post a timeout for the animation + mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); + } + + @Override + public void onAnimationFinished(boolean moveHomeToTop) { + mHandler.removeCallbacks(mCancelAnimationRunnable); + synchronized (mService) { + if (mWindowManager.getRecentsAnimationController() == null) return; + + mWindowManager.inSurfaceTransaction(() -> { + mWindowManager.cleanupRecentsAnimation(); + + // Move the home stack to the front + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + if (homeActivity == null) { + return; + } + + // Restore the launched-behind state + homeActivity.mLaunchTaskBehind = false; + + if (moveHomeToTop) { + // Bring the home stack to the front + final ActivityStack homeStack = homeActivity.getStack(); + homeStack.mNoAnimActivities.add(homeActivity); + homeStack.moveToFront("RecentsAnimation.onAnimationFinished()"); + } else { + // Restore the home stack to its previous position + final ActivityDisplay display = homeActivity.getDisplay(); + display.moveHomeStackBehindStack(mRestoreHomeBehindStack); + } + + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false); + mStackSupervisor.resumeFocusedStackTopActivityLocked(); + }); + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d7a58d99a4d6..3f49f0cd5c15 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1508,6 +1508,10 @@ class DisplayContent extends WindowContainer getVisibleTasks() { + return mTaskStackContainers.getVisibleTasks(); + } + void onStackWindowingModeChanged(TaskStack stack) { mTaskStackContainers.onStackWindowingModeChanged(stack); } @@ -3260,6 +3264,16 @@ class DisplayContent extends WindowContainer getVisibleTasks() { + final ArrayList visibleTasks = new ArrayList<>(); + forAllTasks(task -> { + if (task.isVisible()) { + visibleTasks.add(task); + } + }); + return visibleTasks; + } + /** * Adds the stack to this container. * @see DisplayContent#createStack(int, boolean, StackWindowController) diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java new file mode 100644 index 000000000000..c7ad17b8699b --- /dev/null +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -0,0 +1,373 @@ +/* + * 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.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.app.ActivityManager; +import android.app.ActivityManager.TaskSnapshot; +import android.app.WindowConfiguration; +import android.graphics.GraphicBuffer; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Binder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls a single instance of the remote driven recents animation. In particular, this allows + * the calling SystemUI to animate the visible task windows as a part of the transition. The remote + * runner is provided an animation controller which allows it to take screenshots and to notify + * window manager when the animation is completed. In addition, window manager may also notify the + * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.) + */ +public class RecentsAnimationController { + private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM; + private static final boolean DEBUG = false; + + private final WindowManagerService mService; + private final IRecentsAnimationRunner mRunner; + private final RecentsAnimationCallbacks mCallbacks; + private final ArrayList mPendingAnimations = new ArrayList<>(); + + // The recents component app token that is shown behind the visibile tasks + private AppWindowToken mHomeAppToken; + + // We start the RecentsAnimationController in a pending-start state since we need to wait for + // the wallpaper/activity to draw before we can give control to the handler to start animating + // the visible task surfaces + private boolean mPendingStart = true; + + // Set when the animation has been canceled + private boolean mCanceled = false; + + // Whether or not the input consumer is enabled. The input consumer must be both registered and + // enabled for it to start intercepting touch events. + private boolean mInputConsumerEnabled; + + public interface RecentsAnimationCallbacks { + void onAnimationFinished(boolean moveHomeToTop); + } + + private final IRecentsAnimationController mController = + new IRecentsAnimationController.Stub() { + + @Override + public TaskSnapshot screenshotTask(int taskId) { + if (DEBUG) Log.d(TAG, "screenshotTask(" + taskId + "): mCanceled=" + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return null; + } + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(i); + final Task task = adapter.mTask; + if (task.mTaskId == taskId) { + // TODO: Save this screenshot as the task snapshot? + final Rect taskFrame = new Rect(); + task.getBounds(taskFrame); + final GraphicBuffer buffer = SurfaceControl.captureLayers( + task.getSurfaceControl().getHandle(), taskFrame, 1f); + final AppWindowToken topChild = task.getTopChild(); + final WindowState mainWindow = topChild.findMainWindow(); + return new TaskSnapshot(buffer, topChild.getConfiguration().orientation, + mainWindow.mStableInsets, + ActivityManager.isLowRamDeviceStatic() /* reduced */, + 1.0f /* scale */); + } + } + return null; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void finish(boolean moveHomeToTop) { + if (DEBUG) Log.d(TAG, "finish(" + moveHomeToTop + "): mCanceled=" + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + } + + // Note, the callback will handle its own synchronization, do not lock on WM lock + // prior to calling the callback + mCallbacks.onAnimationFinished(moveHomeToTop); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setInputConsumerEnabled(boolean enabled) { + if (DEBUG) Log.d(TAG, "setInputConsumerEnabled(" + enabled + "): mCanceled=" + + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + + mInputConsumerEnabled = enabled; + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + mService.scheduleAnimationLocked(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + /** + * Initializes a new RecentsAnimationController. + * + * @param remoteAnimationRunner The remote runner which should be notified when the animation is + * ready to start or has been canceled + * @param callbacks Callbacks to be made when the animation finishes + * @param restoreHomeBehindStackId The stack id to restore the home stack behind once the + * animation is complete. Will be passed to the callback. + */ + RecentsAnimationController(WindowManagerService service, + IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, + int displayId) { + mService = service; + mRunner = remoteAnimationRunner; + mCallbacks = callbacks; + + final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + final ArrayList visibleTasks = dc.getVisibleTasks(); + if (visibleTasks.isEmpty()) { + cancelAnimation(); + return; + } + + // Make leashes for each of the visible tasks and add it to the recents animation to be + // started + final int taskCount = visibleTasks.size(); + for (int i = 0; i < taskCount; i++) { + final Task task = visibleTasks.get(i); + final WindowConfiguration config = task.getWindowConfiguration(); + if (config.tasksAreFloating() + || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || config.getActivityType() == ACTIVITY_TYPE_HOME) { + continue; + } + addAnimation(task); + } + + // Adjust the wallpaper visibility for the showing home activity + final AppWindowToken recentsComponentAppToken = + dc.getHomeStack().getTopChild().getTopFullscreenAppToken(); + if (recentsComponentAppToken != null) { + if (DEBUG) Log.d(TAG, "setHomeApp(" + recentsComponentAppToken.getName() + ")"); + mHomeAppToken = recentsComponentAppToken; + final WallpaperController wc = dc.mWallpaperController; + if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) { + dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + dc.setLayoutNeeded(); + } + } + + mService.mWindowPlacerLocked.performSurfacePlacement(); + } + + private void addAnimation(Task task) { + if (DEBUG) Log.d(TAG, "addAnimation(" + task.getName() + ")"); + final SurfaceAnimator anim = new SurfaceAnimator(task, null /* animationFinishedCallback */, + mService.mAnimator::addAfterPrepareSurfacesRunnable, mService); + final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task); + anim.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */); + task.commitPendingTransaction(); + mPendingAnimations.add(taskAdapter); + } + + void startAnimation() { + if (DEBUG) Log.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart); + if (!mPendingStart) { + return; + } + try { + final RemoteAnimationTarget[] appAnimations = + new RemoteAnimationTarget[mPendingAnimations.size()]; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + appAnimations[i] = mPendingAnimations.get(i).createRemoteAnimationApp(); + } + mPendingStart = false; + mRunner.onAnimationStart(mController, appAnimations); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start recents animation", e); + } + } + + void cancelAnimation() { + if (DEBUG) Log.d(TAG, "cancelAnimation()"); + if (mCanceled) { + // We've already canceled the animation + return; + } + mCanceled = true; + try { + mRunner.onAnimationCanceled(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } + + // Clean up and return to the previous app + mCallbacks.onAnimationFinished(false /* moveHomeToTop */); + } + + void cleanupAnimation() { + 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.mCapturedFinishCallback.onAnimationFinished(adapter); + } + mPendingAnimations.clear(); + + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + mService.scheduleAnimationLocked(); + } + + void checkAnimationReady(WallpaperController wallpaperController) { + if (mPendingStart) { + final boolean wallpaperReady = !isHomeAppOverWallpaper() + || (wallpaperController.getWallpaperTarget() != null + && wallpaperController.wallpaperTransitionReady()); + if (wallpaperReady) { + mService.getRecentsAnimationController().startAnimation(); + } + } + } + + boolean isWallpaperVisible(WindowState w) { + return w != null && w.mAppToken != null && mHomeAppToken == w.mAppToken + && isHomeAppOverWallpaper(); + } + + boolean isHomeAppOverWallpaper() { + if (mHomeAppToken == null) { + return false; + } + return mHomeAppToken.windowsCanBeWallpaperTarget(); + } + + WindowState getHomeAppMainWindow() { + if (mHomeAppToken == null) { + return null; + } + return mHomeAppToken.findMainWindow(); + } + + boolean isAnimatingApp(AppWindowToken appToken) { + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final Task task = mPendingAnimations.get(i).mTask; + for (int j = task.getChildCount() - 1; j >= 0; j--) { + final AppWindowToken app = task.getChildAt(j); + if (app == appToken) { + return true; + } + } + } + return false; + } + + boolean isInputConsumerEnabled() { + return mInputConsumerEnabled; + } + + private class TaskAnimationAdapter implements AnimationAdapter { + + private Task mTask; + private SurfaceControl mCapturedLeash; + private OnAnimationFinishedCallback mCapturedFinishCallback; + + TaskAnimationAdapter(Task task) { + mTask = task; + } + + RemoteAnimationTarget createRemoteAnimationApp() { + // TODO: Do we need position and stack bounds? + return new RemoteAnimationTarget(mTask.mTaskId, MODE_CLOSING, mCapturedLeash, + !mTask.fillsParent(), + mTask.getTopVisibleAppMainWindow().mWinAnimator.mLastClipRect, + mTask.getPrefixOrderIndex(), new Point(), new Rect(), + mTask.getWindowConfiguration()); + } + + @Override + public boolean getDetachWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, Transaction t, + OnAnimationFinishedCallback finishCallback) { + mCapturedLeash = animationLeash; + mCapturedFinishCallback = finishCallback; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + cancelAnimation(); + } + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis(); + } + } + + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":"); + pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart); + pw.print(innerPrefix); pw.println("mHomeAppToken=" + mHomeAppToken); + } +} diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 8515dcb69970..7d4eafb07fe9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -160,7 +160,8 @@ class RemoteAnimationController { return new RemoteAnimationTarget(task.mTaskId, getMode(), mCapturedLeash, !mAppWindowToken.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, - mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds); + mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds, + task.getWindowConfiguration()); } private int getMode() { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2cc96c9ee7b6..deed7f17e4e6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -623,6 +623,13 @@ class RootWindowContainer extends WindowContainer { defaultDisplay.pendingLayoutChanges); } + // Defer starting the recents animation until the wallpaper has drawn + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + if (recentsAnimationController != null) { + recentsAnimationController.checkAnimationReady(mWallpaperController); + } + if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0 && !mService.mAppTransition.isReady()) { // At this point, there was a window with a wallpaper that was force hiding other diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 10f1c3a37dcf..0512a08c59db 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -62,7 +62,7 @@ class SurfaceAnimator { * @param addAfterPrepareSurfaces Consumer that takes a runnable and executes it after preparing * surfaces in WM. Can be implemented differently during testing. */ - SurfaceAnimator(Animatable animatable, Runnable animationFinishedCallback, + SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback, Consumer addAfterPrepareSurfaces, WindowManagerService service) { mAnimatable = animatable; mService = service; @@ -71,7 +71,8 @@ class SurfaceAnimator { addAfterPrepareSurfaces); } - private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback, + private OnAnimationFinishedCallback getFinishedCallback( + @Nullable Runnable animationFinishedCallback, Consumer addAfterPrepareSurfaces) { return anim -> { synchronized (mService.mWindowMap) { @@ -97,7 +98,9 @@ class SurfaceAnimator { SurfaceControl.openTransaction(); try { reset(t, true /* destroyLeash */); - animationFinishedCallback.run(); + if (animationFinishedCallback != null) { + animationFinishedCallback.run(); + } } finally { SurfaceControl.mergeToGlobalTransaction(t); SurfaceControl.closeTransaction(); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1218d3bc1b9b..f2ad6fb7a888 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -149,8 +149,17 @@ class WallpaperController { mFindResults.setUseTopWallpaperAsTarget(true); } + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; - if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) { + final boolean isRecentsTransitionTarget = (recentsAnimationController != null + && recentsAnimationController.isWallpaperVisible(w)); + if (isRecentsTransitionTarget) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w); + mFindResults.setWallpaperTarget(w); + return true; + } else if (hasWallpaper && w.isOnScreen() + && (mWallpaperTarget == w || w.isDrawFinishedLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w); mFindResults.setWallpaperTarget(w); if (w == mWallpaperTarget && w.mWinAnimator.isAnimationSet()) { @@ -199,15 +208,22 @@ class WallpaperController { } } - private boolean isWallpaperVisible(WindowState wallpaperTarget) { + private final boolean isWallpaperVisible(WindowState wallpaperTarget) { + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + boolean isAnimatingWithRecentsComponent = recentsAnimationController != null + && recentsAnimationController.isWallpaperVisible(wallpaperTarget); if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) ? wallpaperTarget.mAppToken.isSelfAnimating() : null) - + " prev=" + mPrevWallpaperTarget); + + " prev=" + mPrevWallpaperTarget + + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent); return (wallpaperTarget != null - && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.isSelfAnimating()))) + && (!wallpaperTarget.mObscured + || isAnimatingWithRecentsComponent + || (wallpaperTarget.mAppToken != null + && wallpaperTarget.mAppToken.isSelfAnimating()))) || mPrevWallpaperTarget != null; } @@ -587,6 +603,11 @@ class WallpaperController { mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT; if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG, "*** WALLPAPER DRAW TIMEOUT"); + + // If there was a recents animation in progress, cancel that animation + if (mService.getRecentsAnimationController() != null) { + mService.getRecentsAnimationController().cancelAnimation(); + } return true; } return false; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 42c6ec25a7a8..a0b59efe6ced 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -337,9 +337,9 @@ class WindowContainer extends ConfigurationContainer< } /** Returns true if this window container has the input child. */ - boolean hasChild(WindowContainer child) { + boolean hasChild(E child) { for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer current = mChildren.get(i); + final E current = mChildren.get(i); if (current == child || current.hasChild(child)) { 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 de1e7ecb2bb9..4fb239085e5c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -24,6 +24,8 @@ import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_USER_HANDLE; @@ -123,6 +125,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAssistDataReceiver; +import android.app.WindowConfiguration; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -196,6 +199,7 @@ import android.view.IDockedStackListener; import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; +import android.view.IRecentsAnimationRunner; import android.view.IRotationWatcher; import android.view.IWallpaperVisibilityListener; import android.view.IWindow; @@ -528,6 +532,7 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; AccessibilityController mAccessibilityController; + private RecentsAnimationController mRecentsAnimationController; Watermark mWatermark; StrictModeFlash mStrictModeFlash; @@ -2670,6 +2675,39 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void initializeRecentsAnimation( + IRecentsAnimationRunner recentsAnimationRunner, + RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId) { + synchronized (mWindowMap) { + cancelRecentsAnimation(); + mRecentsAnimationController = new RecentsAnimationController(this, + recentsAnimationRunner, callbacks, displayId); + } + } + + public RecentsAnimationController getRecentsAnimationController() { + return mRecentsAnimationController; + } + + public void cancelRecentsAnimation() { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + // This call will call through to cleanupAnimation() below after the animation is + // canceled + mRecentsAnimationController.cancelAnimation(); + } + } + } + + public void cleanupRecentsAnimation() { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + mRecentsAnimationController.cleanupAnimation(); + mRecentsAnimationController = null; + } + } + } + public void setAppFullscreen(IBinder token, boolean toOpaque) { synchronized (mWindowMap) { final AppWindowToken atoken = mRoot.getAppWindowToken(token); @@ -6327,6 +6365,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation); pw.println(" mLayoutToAnim:"); mAppTransition.dump(pw, " "); + if (mRecentsAnimationController != null) { + pw.print(" mRecentsAnimationController="); pw.println(mRecentsAnimationController); + mRecentsAnimationController.dump(pw, " "); + } } } 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 f0171727409c..24566fcf8f0d 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -573,7 +573,8 @@ public class RecentTasksTest extends ActivityTestsBase { assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, - null, 0)); + null)); + assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation()); } private void testGetTasksApis(boolean expectCallable) {