From 0026a5152c427f9cf2c04da2b23a5ece2fcdb36d Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 16 Jan 2018 16:22:35 -0800 Subject: [PATCH] 2/ Add support for remote Recents animation - Allow the recents component to drive the animation from an app into Recents using the remote animation framework. When initialized, the animation repositions the recents activity behind the currently visible tasks and provides the set of surface controls for the visible tasks. Once complete, the recents component notifies the system whether it has completed the animation into the recents activity, or whether it should restore the previous state. In addition, there is a prescribed delay after which the system automatically cancels the recents animation. Bug: 70180552 Test: go/wm-smoke Test: Manual, swipe up with suitable launcher build Change-Id: Id32f03c7ad2288dce06231cfaa4b21916da511d7 --- 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 704ec8789cb6f..4ef04579b7f81 100644 --- a/Android.bp +++ b/Android.bp @@ -330,6 +330,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 4bcd677e1f4e1..fee58274a5fc6 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 9c15562b58137..56bc184e89c4b 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -65,6 +65,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; @@ -441,8 +442,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 0000000000000..5607b1134e5b1 --- /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 0000000000000..ea6226b3ea69c --- /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 f39e618e169d3..c28c3894482db 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 46ab0e09436ad..aa8d56b3f6c6a 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 fedd3d271be45..edecb5400588f 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; @@ -376,6 +377,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; @@ -449,6 +451,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; @@ -5089,23 +5092,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) { @@ -5122,17 +5118,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 bfb563fd93a80..510a3fa47ec50 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 4dc30ddf4b5b6..8fd754af1a0fe 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 0000000000000..fe576fdaacbed --- /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 d7a58d99a4d69..3f49f0cd5c159 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 0000000000000..c7ad17b8699bc --- /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 8515dcb699707..7d4eafb07fe96 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 2cc96c9ee7b6c..deed7f17e4e66 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 10f1c3a37dcf5..0512a08c59dbb 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 1218d3bc1b9b9..f2ad6fb7a8880 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 42c6ec25a7a85..a0b59efe6ced8 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 de1e7ecb2bb99..4fb239085e5ca 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 f0171727409cd..24566fcf8f0d0 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) {