diff options
| author | 2018-01-26 00:46:34 +0000 | |
|---|---|---|
| committer | 2018-01-26 00:46:34 +0000 | |
| commit | 66f0026f3f27e71dfb764df438ce9016d7c5f08d (patch) | |
| tree | 091d557b2be57df455473a87fe5f907b07816064 | |
| parent | 17546a7e85a5b82b2c5b0aaae8651df780c67d39 (diff) | |
| parent | f34c602108a2725b940946b24194045fefab9f5e (diff) | |
Merge changes from topics "reland_recents_animation", " reland_recents_animation"
* changes:
Fix issue with reparenting stacks on displays.
Revert "Revert "4/ Update SysUI shared lib for Recents transition""
Revert "Revert "3/ Add input consumer to capture touches during a Recents transition""
Revert "Revert "2/ Add support for remote Recents animation""
Revert "Revert "1/ Create display content window controller to position stacks in the display""
37 files changed, 1253 insertions, 113 deletions
diff --git a/Android.bp b/Android.bp index 9645ba6b85a1..5590609659db 100644 --- a/Android.bp +++ b/Android.bp @@ -332,6 +332,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 @@ -207,6 +207,12 @@ public class ActivityOptions { "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 b25deeac75cb..6dcecf197ed2 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; @@ -443,8 +444,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<IBinder> 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<RemoteAnimationTarget> CREATOR diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 3bb3a4c17b8f..1c5e87197750 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -98,11 +98,13 @@ public interface WindowManager extends ViewManager { int DOCKED_BOTTOM = 4; /** @hide */ - final static String INPUT_CONSUMER_PIP = "pip_input_consumer"; + String INPUT_CONSUMER_PIP = "pip_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; + String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + /** @hide */ + String INPUT_CONSUMER_RECENTS_ANIMATION = "recents_animation_input_consumer"; /** * Not set up for a transition. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index cc4bc58fbf9d..da50776708b3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -29,4 +29,9 @@ interface ISystemUiProxy { */ GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform, int rotation); + + /** + * Called when the overview service has started the recents animation. + */ + void onRecentsAnimationStarted(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index c9a6ea9939f5..f9e1069cfe95 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -32,6 +32,7 @@ import android.app.AppGlobals; import android.app.IAssistDataReceiver; import android.app.WindowConfiguration.ActivityType; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -48,7 +49,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.IconDrawableFactory; import android.util.Log; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -243,10 +247,9 @@ public class ActivityManagerWrapper { /** * Starts the recents activity. The caller should manage the thread on which this is called. */ - public void startRecentsActivity(AssistDataReceiverCompat assistDataReceiver, Bundle options, - ActivityOptions opts, int userId, Consumer<Boolean> resultCallback, + public void startRecentsActivity(Intent intent, AssistDataReceiver assistDataReceiver, + RecentsAnimationListener animationHandler, Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { - Bundle activityOptions = opts != null ? opts.toBundle() : null; try { IAssistDataReceiver receiver = null; if (assistDataReceiver != null) { @@ -259,8 +262,24 @@ public class ActivityManagerWrapper { } }; } - ActivityManager.getService().startRecentsActivity(receiver, options, activityOptions, - userId); + IRecentsAnimationRunner runner = null; + if (animationHandler != null) { + runner = new IRecentsAnimationRunner.Stub() { + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps) { + final RecentsAnimationControllerCompat controllerCompat = + new RecentsAnimationControllerCompat(controller); + final RemoteAnimationTargetCompat[] appsCompat = + RemoteAnimationTargetCompat.wrap(apps); + animationHandler.onAnimationStart(controllerCompat, appsCompat); + } + + public void onAnimationCanceled() { + animationHandler.onAnimationCanceled(); + } + }; + } + ActivityManager.getService().startRecentsActivity(intent, receiver, runner); if (resultCallback != null) { resultCallbackHandler.post(new Runnable() { @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiverCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java index cd943f62ea9b..7cd6c512b660 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiverCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java @@ -22,7 +22,7 @@ import android.os.Bundle; /** * Abstract class for assist data receivers. */ -public abstract class AssistDataReceiverCompat { - public abstract void onHandleAssistData(Bundle resultData); - public abstract void onHandleAssistScreenshot(Bitmap screenshot); +public abstract class AssistDataReceiver { + public void onHandleAssistData(Bundle resultData) {} + public void onHandleAssistScreenshot(Bitmap screenshot) {} } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index db4f988a9122..38b8ae8418af 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.systemui.shared.system; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import android.os.Binder; import android.os.IBinder; @@ -29,11 +30,12 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.IWindowManager; import android.view.MotionEvent; +import android.view.WindowManagerGlobal; import java.io.PrintWriter; /** - * Manages the input consumer that allows the SystemUI to control the PiP. + * Manages the input consumer that allows the SystemUI to directly receive touch input. */ public class InputConsumerController { @@ -55,12 +57,12 @@ public class InputConsumerController { } /** - * Input handler used for the PiP input consumer. Input events are batched and consumed with the + * Input handler used for the input consumer. Input events are batched and consumed with the * SurfaceFlinger vsync. */ - private final class PipInputEventReceiver extends BatchedInputEventReceiver { + private final class InputEventReceiver extends BatchedInputEventReceiver { - public PipInputEventReceiver(InputChannel inputChannel, Looper looper) { + public InputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper, Choreographer.getSfInstance()); } @@ -68,7 +70,6 @@ public class InputConsumerController { public void onInputEvent(InputEvent event, int displayId) { boolean handled = true; try { - // To be implemented for input handling over Pip windows if (mListener != null && event instanceof MotionEvent) { MotionEvent ev = (MotionEvent) event; handled = mListener.onTouchEvent(ev); @@ -81,15 +82,35 @@ public class InputConsumerController { private final IWindowManager mWindowManager; private final IBinder mToken; + private final String mName; - private PipInputEventReceiver mInputEventReceiver; + private InputEventReceiver mInputEventReceiver; private TouchListener mListener; private RegistrationListener mRegistrationListener; - public InputConsumerController(IWindowManager windowManager) { + /** + * @param name the name corresponding to the input consumer that is defined in the system. + */ + public InputConsumerController(IWindowManager windowManager, String name) { mWindowManager = windowManager; mToken = new Binder(); - registerInputConsumer(); + mName = name; + } + + /** + * @return A controller for the pip input consumer. + */ + public static InputConsumerController getPipInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP); + } + + /** + * @return A controller for the recents animation input consumer. + */ + public static InputConsumerController getRecentsAnimationInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_RECENTS_ANIMATION); } /** @@ -125,12 +146,12 @@ public class InputConsumerController { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel); + mWindowManager.destroyInputConsumer(mName); + mWindowManager.createInputConsumer(mToken, mName, inputChannel); } catch (RemoteException e) { - Log.e(TAG, "Failed to create PIP input consumer", e); + Log.e(TAG, "Failed to create input consumer", e); } - mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); + mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper()); if (mRegistrationListener != null) { mRegistrationListener.onRegistrationChanged(true /* isRegistered */); } @@ -143,9 +164,9 @@ public class InputConsumerController { public void unregisterInputConsumer() { if (mInputEventReceiver != null) { try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + mWindowManager.destroyInputConsumer(mName); } catch (RemoteException e) { - Log.e(TAG, "Failed to destroy PIP input consumer", e); + Log.e(TAG, "Failed to destroy input consumer", e); } mInputEventReceiver.dispose(); mInputEventReceiver = null; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java new file mode 100644 index 000000000000..9a7abf82c56c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -0,0 +1,61 @@ +/* + * 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.systemui.shared.system; + +import android.app.ActivityManager.TaskSnapshot; +import android.os.RemoteException; +import android.util.Log; +import android.view.IRecentsAnimationController; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +public class RecentsAnimationControllerCompat { + + private static final String TAG = RecentsAnimationControllerCompat.class.getSimpleName(); + + private IRecentsAnimationController mAnimationController; + + public RecentsAnimationControllerCompat(IRecentsAnimationController animationController) { + mAnimationController = animationController; + } + + public ThumbnailData screenshotTask(int taskId) { + try { + TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); + return snapshot != null ? new ThumbnailData(snapshot) : new ThumbnailData(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to screenshot task", e); + return new ThumbnailData(); + } + } + + public void setInputConsumerEnabled(boolean enabled) { + try { + mAnimationController.setInputConsumerEnabled(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set input consumer enabled state", e); + } + } + + public void finish(boolean toHome) { + try { + mAnimationController.finish(toHome); + } catch (RemoteException e) { + Log.e(TAG, "Failed to finish recents animation", e); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java new file mode 100644 index 000000000000..bf6179d70a5e --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -0,0 +1,31 @@ +/* + * 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.systemui.shared.system; + +public interface RecentsAnimationListener { + + /** + * Called when the animation into Recents can start. This call is made on the binder thread. + */ + void onAnimationStart(RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps); + + /** + * Called when the animation into Recents was canceled. This call is made on the binder thread. + */ + void onAnimationCanceled(); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index 244c1b990448..b6e49ae6cc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -77,6 +77,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Binder.restoreCallingIdentity(token); } } + + public void onRecentsAnimationStarted() { + long token = Binder.clearCallingIdentity(); + try { + notifyRecentsAnimationStarted(); + } finally { + Binder.restoreCallingIdentity(token); + } + } }; private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { @@ -214,6 +223,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void notifyRecentsAnimationStarted() { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onRecentsAnimationStarted(); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(TAG_OPS + " state:"); @@ -224,6 +239,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } public interface OverviewProxyListener { - void onConnectionChanged(boolean isConnected); + default void onConnectionChanged(boolean isConnected) {} + default void onRecentsAnimationStarted() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 36531bb727a4..24d0126a1494 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -16,12 +16,10 @@ package com.android.systemui.pip.phone; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; import android.app.ActivityManager; -import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; import android.content.ComponentName; import android.content.Context; @@ -43,6 +41,7 @@ import com.android.systemui.recents.events.component.ExpandPipEvent; import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; @@ -174,7 +173,8 @@ public class PipManager implements BasePipManager { } ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); - mInputConsumerController = new InputConsumerController(mWindowManager); + mInputConsumerController = InputConsumerController.getPipInputConsumer(); + mInputConsumerController.registerInputConsumer(); mMediaController = new PipMediaController(context, mActivityManager); mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController, mInputConsumerController); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 9fb201b82d8c..26fced307bac 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -23,7 +23,6 @@ import android.app.ActivityManager.StackInfo; import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.RemoteAction; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -43,6 +42,7 @@ import com.android.systemui.pip.phone.PipMediaController.ActionListener; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.HidePipMenuEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index c0fed342ef44..b25351731a35 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -46,6 +46,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.R; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 9f4d35e19332..b5fa52378660 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -104,6 +104,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private DeadZone mDeadZone; private final NavigationBarTransitions mBarTransitions; private final OverviewProxyService mOverviewProxyService; + private boolean mRecentsAnimationStarted; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) final static boolean WORKAROUND_INVALID_LAYOUT = true; @@ -206,10 +207,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } - private final OverviewProxyListener mOverviewProxyListener = isConnected -> { - updateSlippery(); - setDisabledFlags(mDisabledFlags, true); - setUpSwipeUpOnboarding(isConnected); + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + @Override + public void onConnectionChanged(boolean isConnected) { + updateSlippery(); + setDisabledFlags(mDisabledFlags, true); + setUpSwipeUpOnboarding(isConnected); + } + + @Override + public void onRecentsAnimationStarted() { + mRecentsAnimationStarted = true; + } }; public NavigationBarView(Context context, AttributeSet attrs) { @@ -273,12 +282,26 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper.onTouchEvent(event)) { return true; } - return super.onTouchEvent(event); + return mRecentsAnimationStarted || super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { - return mGestureHelper.onInterceptTouchEvent(event); + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mRecentsAnimationStarted = false; + } else if (action == MotionEvent.ACTION_UP) { + // If the overview proxy service has not started the recents animation then clean up + // after it to ensure that the nav bar buttons still work + if (mOverviewProxyService.getProxy() != null && !mRecentsAnimationStarted) { + try { + ActivityManager.getService().cancelRecentsAnimation(); + } catch (RemoteException e) { + Log.e(TAG, "Could not cancel recents animation"); + } + } + } + return mRecentsAnimationStarted || mGestureHelper.onInterceptTouchEvent(event); } public void abortCurrentGesture() { diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index 427ccba94fd6..220014fff4ab 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; @@ -50,7 +51,9 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.ConfigurationContainer; +import com.android.server.wm.DisplayWindowController; +import com.android.server.wm.WindowContainerListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -58,7 +61,8 @@ import java.util.ArrayList; * Exactly one of these classes per Display in the system. Capable of holding zero or more * attached {@link ActivityStack}s. */ -class ActivityDisplay extends ConfigurationContainer<ActivityStack> { +class ActivityDisplay extends ConfigurationContainer<ActivityStack> + implements WindowContainerListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM; private static final String TAG_STACK = TAG + POSTFIX_STACK; @@ -100,6 +104,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { // Used in updating the display size private Point mTmpDisplaySize = new Point(); + private DisplayWindowController mWindowContainerController; + ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { mSupervisor = supervisor; mDisplayId = displayId; @@ -108,10 +114,15 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { throw new IllegalStateException("Display does not exist displayId=" + displayId); } mDisplay = display; + mWindowContainerController = createWindowContainerController(); updateBounds(); } + protected DisplayWindowController createWindowContainerController() { + return new DisplayWindowController(mDisplayId, this); + } + void updateBounds() { mDisplay.getSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); @@ -148,7 +159,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private void positionChildAt(ActivityStack stack, int position) { mStacks.remove(stack); - mStacks.add(getTopInsertPosition(stack, position), stack); + final int insertPosition = getTopInsertPosition(stack, position); + mStacks.add(insertPosition, stack); + mWindowContainerController.positionChildAt(stack.getWindowContainerController(), + insertPosition); } private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { @@ -661,6 +675,64 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { && (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 635b38a35889..796290f26305 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; @@ -378,6 +379,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; @@ -451,6 +453,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; @@ -5105,23 +5108,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) { @@ -5138,17 +5134,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/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9d06b0dbab64..4485590c04df 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -642,7 +642,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // TODO: We should probably resolve the windowing mode for the stack on the new display here // so that it end up in a compatible mode in the new display. e.g. split-screen secondary. removeFromDisplay(); + // Reparent the window container before we try to update the position when adding it to + // the new display below mTmpRect2.setEmpty(); + mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */); mStackSupervisor.resumeFocusedStackTopActivityLocked(); @@ -650,7 +653,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // windows that are no longer visible. mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS); - mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); } /** @@ -994,12 +996,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai insertTaskAtTop(task, null); return; } - - task = topTask(); - if (task != null) { - mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), - true /* includingParents */); - } } /** @@ -1024,12 +1020,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (task != null) { insertTaskAtBottom(task); return; - } else { - task = bottomTask(); - if (task != null) { - mWindowContainerController.positionChildAtBottom( - task.getWindowContainerController(), true /* includingParents */); - } } } 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) { - - // The caller specifies that we'd like to be avoided to be moved to the front, - // so be it! - mDoResume = false; - mAvoidMoveToFront = true; + 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; + } } + } 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 6dc384a8831e..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<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getTopStack(); } + ArrayList<Task> getVisibleTasks() { + return mTaskStackContainers.getVisibleTasks(); + } + void onStackWindowingModeChanged(TaskStack stack) { mTaskStackContainers.onStackWindowingModeChanged(stack); } @@ -1802,6 +1806,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo getParent().positionChildAt(position, this, includingParents); } + void positionStackAt(int position, TaskStack child) { + mTaskStackContainers.positionChildAt(position, child, false /* includingParents */); + layoutAndAssignWindowLayersIfNeeded(); + } + int taskIdFromPoint(int x, int y) { for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); @@ -3255,6 +3264,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mSplitScreenPrimaryStack; } + ArrayList<Task> getVisibleTasks() { + final ArrayList<Task> 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/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java new file mode 100644 index 000000000000..ad4957e4fc6f --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayWindowController.java @@ -0,0 +1,85 @@ +/* + * 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.wm; + +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.content.res.Configuration; +import android.util.Slog; + +/** + * Controller for the display container. This is created by activity manager to link activity + * displays to the display content they use in window manager. + */ +public class DisplayWindowController + extends WindowContainerController<DisplayContent, WindowContainerListener> { + + private final int mDisplayId; + + public DisplayWindowController(int displayId, WindowContainerListener listener) { + super(listener, WindowManagerService.getInstance()); + mDisplayId = displayId; + + synchronized (mWindowMap) { + // TODO: Convert to setContainer() from DisplayContent once everything is hooked up. + // Currently we are not setup to register for config changes. + mContainer = mRoot.getDisplayContentOrCreate(displayId); + if (mContainer == null) { + throw new IllegalArgumentException("Trying to add displayId=" + displayId); + } + } + } + + @Override + public void removeContainer() { + // TODO: Pipe through from ActivityDisplay to remove the display + throw new UnsupportedOperationException("To be implemented"); + } + + @Override + public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + // TODO: Pipe through from ActivityDisplay to update the configuration for the display + throw new UnsupportedOperationException("To be implemented"); + } + + /** + * Positions the task stack at the given position in the task stack container. + */ + public void positionChildAt(StackWindowController child, int position) { + synchronized (mWindowMap) { + if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskStackAt: positioning stack=" + child + + " at " + position); + if (mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionTaskStackAt: could not find display=" + mContainer); + return; + } + if (child.mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionTaskStackAt: could not find stack=" + this); + return; + } + mContainer.positionStackAt(position, child.mContainer); + } + } + + @Override + public String toString() { + return "{DisplayWindowController displayId=" + mDisplayId + "}"; + } +} diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 88b7a11f02fd..281e0a8441e2 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS; @@ -86,6 +87,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { private boolean mAddInputConsumerHandle; private boolean mAddPipInputConsumerHandle; private boolean mAddWallpaperInputConsumerHandle; + private boolean mAddRecentsAnimationInputConsumerHandle; private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer = @@ -612,7 +614,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { InputConsumerImpl navInputConsumer; InputConsumerImpl pipInputConsumer; InputConsumerImpl wallpaperInputConsumer; - Rect pipTouchableBounds; + InputConsumerImpl recentsAnimationInputConsumer; boolean inDrag; WallpaperController wallpaperController; @@ -622,11 +624,13 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY); pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY); wallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY); + recentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION, + DEFAULT_DISPLAY); mAddInputConsumerHandle = navInputConsumer != null; mAddPipInputConsumerHandle = pipInputConsumer != null; mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null; + mAddRecentsAnimationInputConsumerHandle = recentsAnimationInputConsumer != null; mTmpRect.setEmpty(); - pipTouchableBounds = mAddPipInputConsumerHandle ? mTmpRect : null; mDisableWallpaperTouchEvents = false; this.inDrag = inDrag; wallpaperController = mService.mRoot.mWallpaperController; @@ -659,12 +663,28 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { final boolean hasFocus = w == mInputFocus; final boolean isVisible = w.isVisibleLw(); + if (mAddRecentsAnimationInputConsumerHandle) { + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + if (recentsAnimationController != null + && recentsAnimationController.hasInputConsumerForApp(w.mAppToken)) { + if (recentsAnimationController.updateInputConsumerForApp( + recentsAnimationInputConsumer, hasFocus)) { + addInputWindowHandle(recentsAnimationInputConsumer.mWindowHandle); + mAddRecentsAnimationInputConsumerHandle = false; + } + // Skip adding the window below regardless of whether there is an input consumer + // to handle it + return; + } + } + if (w.inPinnedWindowingMode()) { if (mAddPipInputConsumerHandle && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) { // Update the bounds of the Pip input consumer to match the window bounds. - w.getBounds(pipTouchableBounds); - pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds); + w.getBounds(mTmpRect); + pipInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect); addInputWindowHandle(pipInputConsumer.mWindowHandle); mAddPipInputConsumerHandle = false; } 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..c7d4b8ed0f16 --- /dev/null +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -0,0 +1,385 @@ +/* + * 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 android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +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<TaskAnimationAdapter> 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; + + private Rect mTmpRect = new Rect(); + + 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<Task> 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(); + mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + } + + 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 hasInputConsumerForApp(AppWindowToken appToken) { + return mInputConsumerEnabled && isAnimatingApp(appToken); + } + + boolean updateInputConsumerForApp(InputConsumerImpl recentsAnimationInputConsumer, + boolean hasFocus) { + // Update the input consumer touchable region to match the home app main window + final WindowState homeAppMainWindow = mHomeAppToken != null + ? mHomeAppToken.findMainWindow() + : null; + if (homeAppMainWindow != null) { + homeAppMainWindow.getBounds(mTmpRect); + recentsAnimationInputConsumer.mWindowHandle.hasFocus = hasFocus; + recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect); + return true; + } + return false; + } + + private boolean isHomeAppOverWallpaper() { + if (mHomeAppToken == null) { + return false; + } + return mHomeAppToken.windowsCanBeWallpaperTarget(); + } + + private 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; + } + + 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<DisplayContent> { 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<Runnable> 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<Runnable> 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 8a500b55b936..1f9255a2b20f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -343,9 +343,9 @@ class WindowContainer<E extends 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/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 96bf49b288c9..10253c570f3f 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; +import com.android.server.wm.DisplayWindowController; import org.mockito.invocation.InvocationOnMock; import android.app.IApplicationThread; @@ -345,7 +346,7 @@ public class ActivityTestsBase { } } - private static class TestActivityDisplay extends ActivityDisplay { + protected static class TestActivityDisplay extends ActivityDisplay { private final ActivityStackSupervisor mSupervisor; TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { @@ -374,6 +375,11 @@ public class ActivityTestsBase { this, stackId, mSupervisor, windowingMode, activityType, onTop); } } + + @Override + protected DisplayWindowController createWindowContainerController() { + return mock(DisplayWindowController.class); + } } private static WindowManagerService prepareMockWindowManager() { 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 5a2110258828..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) { @@ -676,8 +677,8 @@ public class RecentTasksTest extends ActivityTestsBase { @Override public void initialize() { super.initialize(); - mDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); - mOtherDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); + mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); + mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); attachDisplay(mOtherDisplay); attachDisplay(mDisplay); } diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java index fc7562869490..c6ce7e1188e8 100644 --- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java @@ -68,7 +68,7 @@ public class RunningTasksTest extends ActivityTestsBase { // Create a number of stacks with tasks (of incrementing active time) final ActivityStackSupervisor supervisor = mService.mStackSupervisor; final SparseArray<ActivityDisplay> displays = new SparseArray<>(); - final ActivityDisplay display = new ActivityDisplay(supervisor, DEFAULT_DISPLAY); + final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY); displays.put(DEFAULT_DISPLAY, display); final int numStacks = 2; |