From 0dd1586bf7e0faa1d258f2d5c45b8f79317892d6 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Mon, 14 Sep 2020 17:35:39 -0700 Subject: [PATCH] Scaffolding for Shell Transitions Initial rough implementation of Shell Transitions. Shell Transitions can be enabled by setting ENABLE_SHELL_TRANSITIONS via `adb shell setprop persist.debug.shell_transit 1` The main structure behind this is: - TransitionController in Core responsible for handling the collection of transition participants and communicating with the TransitionPlayer in Shell. - Transitions in Shell responsible for starting transitions and playing associated animations. This initial version only supports task open/show/close and does a simple fade. The flow is: 1. activityStarter calls to Transitions.requestStartTransition 2. Transitions will prepare to start and then call WindowOrganizerController.startTransition(). 2. Various operations in Core call TransitionController.collect() to track participants. This collect() acts as a replacement for adding to mOpening/ClosingApps. 3. The legacy executeAppTransition() is wired into TransitionController.setReady() for now. This signal is used to ready a BLASTSyncEngine. 4. When all participants are done drawing, TransitionController will commit any showing visibility changes and then will construct a TransitionInfo object summarizing all the changes needed for animation. This gets sent to Transitions.onTransitionReady() 5. Transitions then plays the transition animation and on completion will call WindowOrganizerController.finishTransition() which will tell TransitionController to do any finishing work (like commiting deferred hide changes). Bug: 161980187 Test: enable the flag and try opening/closing tasks. Also added TransitionControllerTests Change-Id: I80c3a241fc12004a894c26523a1e5828e0c533ff --- .../android/window/ITransitionPlayer.aidl | 63 +++ .../window/IWindowOrganizerController.aidl | 32 ++ core/java/android/window/TransitionInfo.aidl | 20 + core/java/android/window/TransitionInfo.java | 290 +++++++++++ .../android/window/WindowContainerToken.java | 5 + core/java/android/window/WindowOrganizer.java | 57 ++ .../internal/protolog/ProtoLogGroup.java | 2 + data/etc/services.core.protolog.json | 153 ++++++ .../Shell/res/raw/wm_shell_protolog.json | 21 + .../wm/shell/FullscreenTaskListener.java | 10 +- .../android/wm/shell/ShellTaskOrganizer.java | 15 +- .../src/com/android/wm/shell/Transitions.java | 181 +++++++ .../wm/shell/common/AnimationThread.java | 61 +++ .../wm/shell/common/HandlerExecutor.java | 48 ++ .../wm/shell/common/ShellExecutor.java | 35 ++ .../wm/shell/protolog/ShellProtoLogGroup.java | 2 + .../wm/shell/ShellTaskOrganizerTests.java | 7 +- .../systemui/wmshell/WMShellBaseModule.java | 8 +- .../com/android/server/wm/ActivityRecord.java | 16 + .../server/wm/ActivityStackSupervisor.java | 3 +- .../server/wm/ActivityTaskManagerService.java | 4 + .../com/android/server/wm/AppTransition.java | 6 +- .../com/android/server/wm/DisplayContent.java | 6 + .../core/java/com/android/server/wm/Task.java | 25 +- .../server/wm/TaskOrganizerController.java | 2 +- .../com/android/server/wm/Transition.java | 487 ++++++++++++++++++ .../server/wm/TransitionController.java | 225 ++++++++ .../android/server/wm/WindowContainer.java | 26 +- .../server/wm/WindowManagerService.java | 5 +- .../server/wm/WindowOrganizerController.java | 89 +++- .../com/android/server/wm/WindowState.java | 7 +- .../android/server/wm/TransitionTests.java | 178 +++++++ .../android/server/wm/WindowTestsBase.java | 5 + 33 files changed, 2077 insertions(+), 17 deletions(-) create mode 100644 core/java/android/window/ITransitionPlayer.aidl create mode 100644 core/java/android/window/TransitionInfo.aidl create mode 100644 core/java/android/window/TransitionInfo.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java create mode 100644 services/core/java/com/android/server/wm/Transition.java create mode 100644 services/core/java/com/android/server/wm/TransitionController.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TransitionTests.java diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl new file mode 100644 index 0000000000000..a8a29b26a1485 --- /dev/null +++ b/core/java/android/window/ITransitionPlayer.aidl @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 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.window; + +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +/** + * Implemented by WMShell to initiate and play transition animations. + * The flow (with {@link IWindowOrganizerController}) looks like this: + *

    + *
  1. Core starts an activity and calls {@link #requestStartTransition} + *
  2. This TransitionPlayer impl does whatever, then calls + * {@link IWindowOrganizerController#startTransition} to tell Core to formally start (until + * this happens, Core will collect changes on the transition, but won't consider it ready to + * animate). + *
  3. Once all collected changes on the transition have finished drawing, Core will then call + * {@link #onTransitionReady} here to delegate the actual animation. + *
  4. Once this TransitionPlayer impl finishes animating, it notifies Core via + * {@link IWindowOrganizerController#finishTransition}. At this point, ITransitionPlayer's + * responsibilities end. + * + * + * {@hide} + */ +oneway interface ITransitionPlayer { + + /** + * Called when all participants of a transition are ready to animate. This is in response to + * {@link IWindowOrganizerController#startTransition}. + * + * @param transitionToken An identifying token for the transition that is now ready to animate. + * @param info A collection of all the changes encapsulated by this transition. + * @param t A surface transaction containing the surface state prior to animating. + */ + void onTransitionReady(in IBinder transitionToken, in TransitionInfo info, + in SurfaceControl.Transaction t); + + /** + * Called when something in WMCore requires a transition to play -- for example when an Activity + * is started in a new Task. + * + * @param type The {@link WindowManager#TransitionType} of the transition to start. + * @param transitionToken An identifying token for the transition that needs to be started. + * Pass this to {@link IWindowOrganizerController#startTransition}. + */ + void requestStartTransition(int type, in IBinder transitionToken); +} diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 7e9c783c83c6b..0cd9b366bf5c2 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -18,8 +18,10 @@ package android.window; import android.view.SurfaceControl; +import android.os.IBinder; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -45,6 +47,30 @@ interface IWindowOrganizerController { int applySyncTransaction(in WindowContainerTransaction t, in IWindowContainerTransactionCallback callback); + /** + * Starts a transition. + * @param type The transition type. + * @param transitionToken A token associated with the transition to start. If null, a new + * transition will be created of the provided type. + * @param t Operations that are part of the transition. + * @return a token representing the transition. This will just be transitionToken if it was + * non-null. + */ + IBinder startTransition(int type, in @nullable IBinder transitionToken, + in @nullable WindowContainerTransaction t); + + /** + * Finishes a transition. This must be called for all created transitions. + * @param transitionToken Which transition to finish + * @param t Changes to make before finishing but in the same SF Transaction. Can be null. + * @param callback Called when t is finished applying. + * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be + * negative if no sync transaction was attached (null t or callback) + */ + int finishTransition(in IBinder transitionToken, + in @nullable WindowContainerTransaction t, + in IWindowContainerTransactionCallback callback); + /** @return An interface enabling the management of task organizers. */ ITaskOrganizerController getTaskOrganizerController(); @@ -61,4 +87,10 @@ interface IWindowOrganizerController { * @return true if the screenshot was successful, false otherwise. */ boolean takeScreenshot(in WindowContainerToken token, out SurfaceControl outSurfaceControl); + + /** + * Registers a transition player with Core. There is only one of these at a time and calling + * this will replace the existing one if set. + */ + void registerTransitionPlayer(in ITransitionPlayer player); } diff --git a/core/java/android/window/TransitionInfo.aidl b/core/java/android/window/TransitionInfo.aidl new file mode 100644 index 0000000000000..6c33e9737f6a8 --- /dev/null +++ b/core/java/android/window/TransitionInfo.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2020 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.window; + +parcelable TransitionInfo; +parcelable TransitionInfo.Change; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java new file mode 100644 index 0000000000000..34d1d4e8699d0 --- /dev/null +++ b/core/java/android/window/TransitionInfo.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2020 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.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; +import android.view.WindowManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Used to communicate information about what is changing during a transition to a TransitionPlayer. + * @hide + */ +public final class TransitionInfo implements Parcelable { + + /** No transition mode. This is a placeholder, don't use this as an actual mode. */ + public static final int TRANSIT_NONE = 0; + + /** The container didn't exist before but will exist and be visible after. */ + public static final int TRANSIT_OPEN = 1; + + /** The container existed and was visible before but won't exist after. */ + public static final int TRANSIT_CLOSE = 2; + + /** The container existed before but was invisible and will be visible after. */ + public static final int TRANSIT_SHOW = 3; + + /** The container is going from visible to invisible but it will still exist after. */ + public static final int TRANSIT_HIDE = 4; + + /** The container exists and is visible before and after but it changes. */ + public static final int TRANSIT_CHANGE = 5; + + /** @hide */ + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_NONE, + TRANSIT_OPEN, + TRANSIT_CLOSE, + TRANSIT_SHOW, + TRANSIT_HIDE, + TRANSIT_CHANGE + }) + public @interface TransitionMode {} + + private final @WindowManager.TransitionType int mType; + private final ArrayList mChanges = new ArrayList<>(); + + /** @hide */ + public TransitionInfo(@WindowManager.TransitionType int type) { + mType = type; + } + + private TransitionInfo(Parcel in) { + mType = in.readInt(); + in.readList(mChanges, null /* classLoader */); + } + + @Override + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeList(mChanges); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public TransitionInfo createFromParcel(Parcel in) { + return new TransitionInfo(in); + } + + @Override + public TransitionInfo[] newArray(int size) { + return new TransitionInfo[size]; + } + }; + + @Override + /** @hide */ + public int describeContents() { + return 0; + } + + public int getType() { + return mType; + } + + @NonNull + public List getChanges() { + return mChanges; + } + + /** + * @return the Change that a window is undergoing or {@code null} if not directly + * represented. + */ + @Nullable + public Change getChange(@NonNull WindowContainerToken token) { + for (int i = mChanges.size() - 1; i >= 0; --i) { + if (mChanges.get(i).mContainer == token) { + return mChanges.get(i); + } + } + return null; + } + + /** + * Add a {@link Change} to this transition. + */ + public void addChange(@NonNull Change change) { + mChanges.add(change); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{t=" + mType + " c=["); + for (int i = 0; i < mChanges.size(); ++i) { + if (i > 0) { + sb.append(','); + } + sb.append(mChanges.get(i)); + } + sb.append("]}"); + return sb.toString(); + } + + /** Converts a transition mode/action to its string representation. */ + @NonNull + public static String modeToString(@TransitionMode int mode) { + switch(mode) { + case TRANSIT_NONE: return "NONE"; + case TRANSIT_OPEN: return "OPEN"; + case TRANSIT_CLOSE: return "CLOSE"; + case TRANSIT_SHOW: return "SHOW"; + case TRANSIT_HIDE: return "HIDE"; + case TRANSIT_CHANGE: return "CHANGE"; + default: return ""; + } + } + + /** Represents the change a WindowContainer undergoes during a transition */ + public static final class Change implements Parcelable { + private final WindowContainerToken mContainer; + private WindowContainerToken mParent; + private final SurfaceControl mLeash; + private int mMode = TRANSIT_NONE; + private final Rect mStartBounds = new Rect(); + private final Rect mEndBounds = new Rect(); + + public Change(@NonNull WindowContainerToken container, @NonNull SurfaceControl leash) { + mContainer = container; + mLeash = leash; + } + + private Change(Parcel in) { + mContainer = WindowContainerToken.CREATOR.createFromParcel(in); + mParent = in.readParcelable(WindowContainerToken.class.getClassLoader()); + mLeash = new SurfaceControl(); + mLeash.readFromParcel(in); + mMode = in.readInt(); + mStartBounds.readFromParcel(in); + mEndBounds.readFromParcel(in); + } + + /** Sets the parent of this change's container. The parent must be a participant or null. */ + public void setParent(@Nullable WindowContainerToken parent) { + mParent = parent; + } + + /** Sets the transition mode for this change */ + public void setMode(@TransitionMode int mode) { + mMode = mode; + } + + /** Sets the bounds this container occupied before the change */ + public void setStartBounds(@Nullable Rect rect) { + mStartBounds.set(rect); + } + + /** Sets the bounds this container will occupy after the change */ + public void setEndBounds(@Nullable Rect rect) { + mEndBounds.set(rect); + } + + /** @return the container that is changing */ + @NonNull + public WindowContainerToken getContainer() { + return mContainer; + } + + /** + * @return the parent of the changing container. This is the parent within the participants, + * not necessarily the actual parent. + */ + @Nullable + public WindowContainerToken getParent() { + return mParent; + } + + /** @return which action this change represents. */ + public @TransitionMode int getMode() { + return mMode; + } + + /** + * @return the bounds of the container before the change. It may be empty if the container + * is coming into existence. + */ + @NonNull + public Rect getStartBounds() { + return mStartBounds; + } + + /** + * @return the bounds of the container after the change. It may be empty if the container + * is disappearing. + */ + @NonNull + public Rect getEndBounds() { + return mEndBounds; + } + + /** @return the leash or surface to animate for this container */ + @NonNull + public SurfaceControl getLeash() { + return mLeash; + } + + @Override + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + mContainer.writeToParcel(dest, flags); + dest.writeParcelable(mParent, 0); + mLeash.writeToParcel(dest, flags); + dest.writeInt(mMode); + mStartBounds.writeToParcel(dest, flags); + mEndBounds.writeToParcel(dest, flags); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public Change createFromParcel(Parcel in) { + return new Change(in); + } + + @Override + public Change[] newArray(int size) { + return new Change[size]; + } + }; + + @Override + /** @hide */ + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + + " m=" + modeToString(mMode) + " sb=" + mStartBounds + + " eb=" + mEndBounds + "}"; + } + } +} diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java index c92ccae66ff8f..96e8b44d13ccf 100644 --- a/core/java/android/window/WindowContainerToken.java +++ b/core/java/android/window/WindowContainerToken.java @@ -77,6 +77,11 @@ public final class WindowContainerToken implements Parcelable { return mRealToken.asBinder().hashCode(); } + @Override + public String toString() { + return "WCT{" + mRealToken + "}"; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof WindowContainerToken)) { diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 97a97d9984f92..5ac19fa685d79 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -19,8 +19,10 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityTaskManager; +import android.os.IBinder; import android.os.RemoteException; import android.util.Singleton; import android.view.SurfaceControl; @@ -65,6 +67,48 @@ public class WindowOrganizer { } } + /** + * Start a transition. + * @param type The type of the transition. This is ignored if a transitionToken is provided. + * @param transitionToken An existing transition to start. If null, a new transition is created. + * @param t The set of window operations that are part of this transition. + * @return A token identifying the transition. This will be the same as transitionToken if it + * was provided. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @NonNull + public IBinder startTransition(int type, @Nullable IBinder transitionToken, + @Nullable WindowContainerTransaction t) { + try { + return getWindowOrganizerController().startTransition(type, transitionToken, t); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Finishes a running transition. + * @param transitionToken The transition to finish. Can't be null. + * @param t A set of window operations to apply before finishing. + * @param callback A sync callback (if provided). See {@link #applySyncTransaction}. + * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}. + * + * @hide + */ + @SuppressLint("ExecutorRegistration") + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public int finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t, + @Nullable WindowContainerTransactionCallback callback) { + try { + return getWindowOrganizerController().finishTransition(transitionToken, t, + callback != null ? callback.mInterface : null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Take a screenshot for a specified Window * @param token The token for the WindowContainer that should get a screenshot taken. @@ -87,6 +131,19 @@ public class WindowOrganizer { } } + /** + * Register an ITransitionPlayer to handle transition animations. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + try { + getWindowOrganizerController().registerTransitionPlayer(player); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) IWindowOrganizerController getWindowOrganizerController() { return IWindowOrganizerControllerSingleton.get(); diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 9874c6aabf042..50ba42fdae2f7 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -74,6 +74,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9eaeed11c02f8..c53ea8789c8b5 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -205,6 +205,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1844540996": { + "message": " Initial targets: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1838803135": { "message": "Attempted to set windowing mode to a display that does not exist: %d", "level": "WARN", @@ -343,12 +349,24 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-1587921395": { + "message": " Top targets: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1568331821": { "message": "Enabling listeners", "level": "VERBOSE", "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotation.java" }, + "-1567866547": { + "message": "Collecting in transition %d: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1554521902": { "message": "showInsets(ime) was requested by different window: %s ", "level": "WARN", @@ -421,6 +439,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1452274694": { + "message": " CAN PROMOTE: promoting to parent %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1443029505": { "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)", "level": "INFO", @@ -475,6 +499,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-1375751630": { + "message": " --- Start combine pass ---", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1364754753": { "message": "Task vanished taskId=%d", "level": "VERBOSE", @@ -787,6 +817,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-855366859": { + "message": " merging children in from %s: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-853404763": { "message": "\twallpaper=%s", "level": "DEBUG", @@ -871,6 +907,12 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-703543418": { + "message": " check sibling %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-694710814": { "message": "Pausing rotation during drag", "level": "DEBUG", @@ -925,6 +967,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-622017164": { + "message": "Finish Transition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-618015844": { "message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s", "level": "INFO", @@ -979,12 +1027,24 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowAnimator.java" }, + "-532081937": { + "message": " Commit activity becoming invisible: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-519504830": { "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s", "level": "VERBOSE", "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "-509601642": { + "message": " checking %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-507657818": { "message": "Window %s is already added", "level": "WARN", @@ -1027,6 +1087,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, + "-446752714": { + "message": " SKIP: sibling contains top target %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-445944810": { "message": "finish(%b): mCanceled=%b", "level": "DEBUG", @@ -1153,6 +1219,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-302335479": { + "message": " remove from topTargets %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-272719931": { "message": "startLockTaskModeLocked: %s", "level": "WARN", @@ -1441,12 +1513,24 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" }, + "182319432": { + "message": " remove from targets %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "184362060": { "message": "screenshotTask(%d): mCanceled=%b", "level": "DEBUG", "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, + "184610856": { + "message": "Start calculating TransitionInfo based on participants: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "186668272": { "message": "Now changing app %s", "level": "VERBOSE", @@ -1525,6 +1609,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "259206414": { + "message": "Creating Transition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "269576220": { "message": "Resuming rotation after drag", "level": "DEBUG", @@ -1657,6 +1747,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "430260320": { + "message": " sibling is a top target with mode %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "435494046": { "message": "Attempted to add window to a display for which the application does not have access: %d. Aborting.", "level": "WARN", @@ -1711,6 +1807,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransition.java" }, + "528150092": { + "message": " keep as target %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "531242746": { "message": " THUMBNAIL %s: CREATE", "level": "INFO", @@ -1903,6 +2005,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "744171317": { + "message": " SKIP: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "745391677": { "message": " CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s", "level": "INFO", @@ -1921,6 +2029,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskPositioner.java" }, + "793568608": { + "message": " SKIP: sibling is visible but not part of transition", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "794570322": { "message": "Now closing app %s", "level": "VERBOSE", @@ -1957,6 +2071,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "849147756": { + "message": "Finish collecting in transition %d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "853091290": { "message": "Moved stack=%s behind stack=%s", "level": "DEBUG", @@ -2041,6 +2161,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "996960396": { + "message": "Starting Transition %d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1000601037": { "message": "SyncSet{%x:%d} Set ready", "level": "VERBOSE", @@ -2101,6 +2227,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1115248873": { + "message": "Calling onTransitionReady: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1115417974": { "message": "FORCED DISPLAY SIZE: %dx%d", "level": "INFO", @@ -2125,6 +2257,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "1186730970": { + "message": " no common mode yet, so set it", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1208313423": { "message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d", "level": "WARN", @@ -2293,6 +2431,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "1469310004": { + "message": " SKIP: common mode mismatch. was %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1495525537": { "message": "createWallpaperAnimations()", "level": "DEBUG", @@ -2509,6 +2653,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1794249572": { + "message": "Requesting StartTransition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "1822843721": { "message": "Aborted starting %s: startingData=%s", "level": "VERBOSE", @@ -2826,6 +2976,9 @@ "WM_DEBUG_WINDOW_ORGANIZER": { "tag": "WindowManager" }, + "WM_DEBUG_WINDOW_TRANSITIONS": { + "tag": "WindowManager" + }, "WM_ERROR": { "tag": "WindowManager" }, diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index a13e98c0d1ada..227eec2adc874 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -1,12 +1,24 @@ { "version": "1.0.0", "messages": { + "-1534364071": { + "message": "onTransitionReady %s: %s", + "level": "VERBOSE", + "group": "WM_SHELL_TRANSITIONS", + "at": "com\/android\/wm\/shell\/Transitions.java" + }, "-1501874464": { "message": "Fullscreen Task Appeared: #%d", "level": "VERBOSE", "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java" }, + "-1480787369": { + "message": "Transition requested: type=%d %s", + "level": "VERBOSE", + "group": "WM_SHELL_TRANSITIONS", + "at": "com\/android\/wm\/shell\/Transitions.java" + }, "-1340279385": { "message": "Remove listener=%s", "level": "VERBOSE", @@ -31,6 +43,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "-191422040": { + "message": "Transition animations finished, notifying core %s", + "level": "VERBOSE", + "group": "WM_SHELL_TRANSITIONS", + "at": "com\/android\/wm\/shell\/Transitions.java" + }, "157713005": { "message": "Task info changed taskId=%d", "level": "VERBOSE", @@ -53,6 +71,9 @@ "groups": { "WM_SHELL_TASK_ORG": { "tag": "WindowManagerShell" + }, + "WM_SHELL_TRANSITIONS": { + "tag": "WindowManagerShell" } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index b275331fa9539..9d6271bca426d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -50,9 +50,13 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { // properties in a bad state). t.setPosition(leash, 0, 0); t.setWindowCrop(leash, null); - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); + // TODO(shell-transitions): Eventually set everything in transition so there's no + // SF Transaction here. + if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index d650a958a2036..8f496d01c83b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -28,7 +28,9 @@ import android.window.TaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; @@ -59,16 +61,25 @@ public class ShellTaskOrganizer extends TaskOrganizer { // require us to report to both old and new listeners) private final SparseArray> mTasks = new SparseArray<>(); - public ShellTaskOrganizer(SyncTransactionQueue syncQueue) { + // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks + private final Transitions mTransitions; + + public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool, + ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(); addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN); + mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); + if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, - SyncTransactionQueue syncQueue) { + SyncTransactionQueue syncQueue, TransactionPool transactionPool, + ShellExecutor mainExecutor, ShellExecutor animExecutor) { super(taskOrganizerController); addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN); + mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); + if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java new file mode 100644 index 0000000000000..36e49d9fd770d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2020 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.wm.shell; + +import static android.window.TransitionInfo.TRANSIT_CLOSE; +import static android.window.TransitionInfo.TRANSIT_HIDE; +import static android.window.TransitionInfo.TRANSIT_OPEN; +import static android.window.TransitionInfo.TRANSIT_SHOW; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.os.IBinder; +import android.os.SystemProperties; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.ITransitionPlayer; +import android.window.TransitionInfo; +import android.window.WindowOrganizer; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.ArrayList; + +/** Plays transition animations */ +public class Transitions extends ITransitionPlayer.Stub { + private static final String TAG = "ShellTransitions"; + + /** Set to {@code true} to enable shell transitions. */ + public static final boolean ENABLE_SHELL_TRANSITIONS = + SystemProperties.getBoolean("persist.debug.shell_transit", false); + + private final WindowOrganizer mOrganizer; + private final TransactionPool mTransactionPool; + private final ShellExecutor mMainExecutor; + private final ShellExecutor mAnimExecutor; + + /** Keeps track of currently tracked transitions and all the animations associated with each */ + private final ArrayMap> mActiveTransitions = new ArrayMap<>(); + + Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, + @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { + mOrganizer = organizer; + mTransactionPool = pool; + mMainExecutor = mainExecutor; + mAnimExecutor = animExecutor; + } + + // TODO(shell-transitions): real animations + private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash, + boolean show) { + final float end = show ? 1.f : 0.f; + final float start = 1.f - end; + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(start, end); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setAlpha(leash, end); + transaction.apply(); + mTransactionPool.release(transaction); + mMainExecutor.execute(() -> { + mActiveTransitions.get(transition).remove(va); + onFinish(transition); + }); + }; + va.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationRepeat(Animator animation) { } + }); + mActiveTransitions.get(transition).add(va); + mAnimExecutor.execute(va::start); + } + + private static boolean isOpeningType(@WindowManager.TransitionType int legacyType) { + // TODO(shell-transitions): consider providing and using z-order vs the global type for + // this determination. + return legacyType == WindowManager.TRANSIT_TASK_OPEN + || legacyType == WindowManager.TRANSIT_TASK_TO_FRONT + || legacyType == WindowManager.TRANSIT_TASK_OPEN_BEHIND + || legacyType == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; + } + + @Override + public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info, + @NonNull SurfaceControl.Transaction t) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", + transitionToken, info); + // start task + mMainExecutor.execute(() -> { + if (!mActiveTransitions.containsKey(transitionToken)) { + Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken + + " expecting one of " + mActiveTransitions.keySet()); + } + if (mActiveTransitions.get(transitionToken) != null) { + throw new IllegalStateException("Got a duplicate onTransitionReady call for " + + transitionToken); + } + mActiveTransitions.put(transitionToken, new ArrayList<>()); + for (int i = 0; i < info.getChanges().size(); ++i) { + final SurfaceControl leash = info.getChanges().get(i).getLeash(); + final int mode = info.getChanges().get(i).getMode(); + if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) { + t.show(leash); + t.setMatrix(leash, 1, 0, 0, 1); + if (isOpeningType(info.getType())) { + t.setAlpha(leash, 0.f); + startExampleAnimation(transitionToken, leash, true /* show */); + } else { + t.setAlpha(leash, 1.f); + } + } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) { + if (!isOpeningType(info.getType())) { + startExampleAnimation(transitionToken, leash, false /* show */); + } + } + } + t.apply(); + onFinish(transitionToken); + }); + } + + @MainThread + private void onFinish(IBinder transition) { + if (!mActiveTransitions.get(transition).isEmpty()) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Transition animations finished, notifying core %s", transition); + mActiveTransitions.remove(transition); + mOrganizer.finishTransition(transition, null, null); + } + + @Override + public void requestStartTransition(int type, @NonNull IBinder transitionToken) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s", + type, transitionToken); + mMainExecutor.execute(() -> { + if (mActiveTransitions.containsKey(transitionToken)) { + throw new RuntimeException("Transition already started " + transitionToken); + } + IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */); + mActiveTransitions.put(transition, null); + }); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java new file mode 100644 index 0000000000000..96b9f86673fc7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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.wm.shell.common; + +import static android.os.Process.THREAD_PRIORITY_DISPLAY; + +import android.annotation.NonNull; +import android.os.HandlerThread; +import android.util.Singleton; + +/** + * A singleton thread for Shell to run animations on. + */ +public class AnimationThread extends HandlerThread { + private ShellExecutor mExecutor; + + private AnimationThread() { + super("wmshell.anim", THREAD_PRIORITY_DISPLAY); + } + + /** Get the singleton instance of this thread */ + public static AnimationThread instance() { + return sAnimationThreadSingleton.get(); + } + + /** + * @return a shared {@link ShellExecutor} associated with this thread + * @hide + */ + @NonNull + public ShellExecutor getExecutor() { + if (mExecutor == null) { + mExecutor = new HandlerExecutor(getThreadHandler()); + } + return mExecutor; + } + + private static final Singleton sAnimationThreadSingleton = + new Singleton() { + @Override + protected AnimationThread create() { + final AnimationThread animThread = new AnimationThread(); + animThread.start(); + return animThread; + } + }; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java new file mode 100644 index 0000000000000..cd75840b8c711 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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.wm.shell.common; + +import android.annotation.NonNull; +import android.os.Handler; + +/** Executor implementation which is backed by a Handler. */ +public class HandlerExecutor implements ShellExecutor { + private final Handler mHandler; + + public HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + + @Override + public void executeDelayed(@NonNull Runnable r, long delayMillis) { + if (!mHandler.postDelayed(r, delayMillis)) { + throw new RuntimeException(mHandler + " is probably exiting"); + } + } + + @Override + public void removeCallbacks(@NonNull Runnable r) { + mHandler.removeCallbacks(r); + } + + @Override + public void execute(@NonNull Runnable command) { + if (!mHandler.post(command)) { + throw new RuntimeException(mHandler + " is probably exiting"); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java new file mode 100644 index 0000000000000..aafe2407a1eac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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.wm.shell.common; + +import java.util.concurrent.Executor; + +/** + * Super basic Executor interface that adds support for delayed execution and removing callbacks. + * Intended to wrap Handler while better-supporting testing. + */ +public interface ShellExecutor extends Executor { + /** + * See {@link android.os.Handler#postDelayed(Runnable, long)}. + */ + void executeDelayed(Runnable r, long delayMillis); + + /** + * See {@link android.os.Handler#removeCallbacks}. + */ + void removeCallbacks(Runnable r); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index e3029e55a2143..a0ce9dabffe6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -28,6 +28,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { // with those in the framework ProtoLogGroup WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 823e0b7f03c55..1bc5cea40a8b5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -33,7 +33,9 @@ import android.window.ITaskOrganizerController; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; import org.junit.Before; import org.junit.Test; @@ -55,6 +57,8 @@ public class ShellTaskOrganizerTests { ShellTaskOrganizer mOrganizer; private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class); + private final TransactionPool mTransactionPool = mock(TransactionPool.class); + private final ShellExecutor mTestExecutor = mock(ShellExecutor.class); private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener { final ArrayList appeared = new ArrayList<>(); @@ -85,7 +89,8 @@ public class ShellTaskOrganizerTests { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue); + mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue, + mTransactionPool, mTestExecutor, mTestExecutor); } @Test diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index bb3b1b42f1201..970d5001172ee 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -32,8 +32,10 @@ import com.android.systemui.util.DeviceConfigProxy; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.animation.FlingAnimationUtils; +import com.android.wm.shell.common.AnimationThread; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TransactionPool; @@ -132,8 +134,10 @@ public abstract class WMShellBaseModule { @SysUISingleton @Provides - static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue) { - ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue); + static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue, + @Main Handler handler, TransactionPool transactionPool) { + ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool, + new HandlerExecutor(handler), AnimationThread.instance().getExecutor()); organizer.registerOrganizer(); return organizer; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f29ad2434dc96..2355ed3272eaa 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1308,6 +1308,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); if (prevDc.mOpeningApps.remove(this)) { // Transfer opening transition to new display. mDisplayContent.mOpeningApps.add(this); @@ -3234,6 +3236,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this); waitingToShow = false; + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); // Defer removal of this activity when either a child is animating, or app transition is on // going. App transition animation might be applied on the parent stack not on the activity, // but the actual frame buffer is associated with the activity, so we have to keep the @@ -3245,6 +3249,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (getDisplayContent().mAppTransition.isTransitionSet()) { getDisplayContent().mClosingApps.add(this); delayed = true; + } else if (mAtmService.getTransitionController().inTransition()) { + delayed = true; } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, @@ -4076,6 +4082,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mVisible; } + @Override + boolean isVisibleRequested() { + return mVisibleRequested; + } + void setVisible(boolean visible) { if (visible != mVisible) { mVisible = visible; @@ -4206,6 +4217,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A transferStartingWindowFromHiddenAboveTokenIfNeeded(); } + // TODO(b/169035022): move to a more-appropriate place. + mAtmService.getTransitionController().collect(this); + if (!visible && mAtmService.getTransitionController().inTransition()) { + return; + } // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. // Note that we ignore display frozen since we want the opening / closing transition type diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 783f8e8642ae6..3ef383b2eed79 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1830,7 +1830,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { final ActivityRecord s = mStoppingActivities.get(i); final boolean animating = s.isAnimating(TRANSITION | PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + || mService.getTransitionController().inTransition(s); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible + " animating=" + animating + " finishing=" + s.finishing); if (!animating || mService.mShuttingDown) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0402140c6fc1a..c0e31f90b0ae3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -958,6 +958,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mLockTaskController; } + TransitionController getTransitionController() { + return mWindowOrganizerController.getTransitionController(); + } + /** * Return the global configuration used by the process corresponding to the input pid. This is * usually the global configuration with some overrides specific to that process. diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f76108f332d19..0e47ea8058f1e 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -2214,6 +2214,10 @@ public class AppTransition implements Dump { */ boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent, @TransitionFlags int flags, boolean forceOverride) { + if (mService.mAtmService.getTransitionController().adaptLegacyPrepare( + transit, flags, forceOverride)) { + return false; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d " + "Callers=%s", @@ -2255,7 +2259,7 @@ public class AppTransition implements Dump { || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; } - private static boolean isKeyguardTransit(int transit) { + static boolean isKeyguardTransit(int transit) { return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE || transit == TRANSIT_KEYGUARD_UNOCCLUDE; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 565a64557e4b7..22b446d32ecc6 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2281,6 +2281,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } + @Override + boolean isVisibleRequested() { + return isVisible(); + } + @Override void onAppTransitionDone() { super.onAppTransitionDone(); @@ -4441,6 +4446,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void executeAppTransition() { + mAtmService.getTransitionController().setReady(); if (mAppTransition.isTransitionSet()) { ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Execute app transition: %s, displayId: %d Callers=%s", diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a236699209a0b..22776e0d9ee0d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2372,6 +2372,7 @@ class Task extends WindowContainer { private void initializeChangeTransition(Rect startBounds) { mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE, false /* alwaysKeepCurrent */, 0, false /* forceOverride */); + mAtmService.getTransitionController().collect(this); mDisplayContent.mChangingContainers.add(this); mSurfaceFreezer.freeze(getPendingTransaction(), startBounds); @@ -4761,6 +4762,16 @@ class Task extends WindowContainer { return mTaskOrganizer != null; } + @Override + boolean showSurfaceOnCreation() { + // Organized tasks handle their own surface visibility + final boolean willBeOrganized = + mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode()) + && isRootTask(); + return !mAtmService.getTransitionController().isShellTransitionsEnabled() + || !willBeOrganized; + } + @Override protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { /** @@ -4781,7 +4792,9 @@ class Task extends WindowContainer { // hide it to allow the task organizer to show it when it is properly reparented. We // skip this for tasks created by the organizer because they can synchronously update // the leash before new children are added to the task. - if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) { + if (!mAtmService.getTransitionController().isShellTransitionsEnabled() + && !mCreatedByOrganizer + && mTaskOrganizer != null && !prevHasBeenVisible) { getSyncTransaction().hide(getSurfaceControl()); commitPendingTransaction(); } @@ -6362,7 +6375,15 @@ class Task extends WindowContainer { transit = TRANSIT_TASK_OPEN; } } - dc.prepareAppTransition(transit, keepCurTransition); + if (mAtmService.getTransitionController().isShellTransitionsEnabled() + // TODO(shell-transitions): eventually all transitions. + && transit == TRANSIT_TASK_OPEN) { + Transition transition = + mAtmService.getTransitionController().requestTransition(transit); + transition.collect(task); + } else { + dc.prepareAppTransition(transit, keepCurTransition); + } mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index e07c5677214bb..8201d108c883c 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -354,7 +354,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { : null; } - private boolean isSupportedWindowingMode(int winMode) { + boolean isSupportedWindowingMode(int winMode) { return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java new file mode 100644 index 0000000000000..fc67cd22ee69a --- /dev/null +++ b/services/core/java/com/android/server/wm/Transition.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2020 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.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; +import android.view.Display; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.window.TransitionInfo; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.common.ProtoLog; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +/** + * Represents a logical transition. + * @see TransitionController + */ +class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { + private static final String TAG = "Transition"; + + /** The transition has been created and is collecting, but hasn't formally started. */ + private static final int STATE_COLLECTING = 0; + + /** + * The transition has formally started. It is still collecting but will stop once all + * participants are ready to animate (finished drawing). + */ + private static final int STATE_STARTED = 1; + + /** + * This transition is currently playing its animation and can no longer collect or be changed. + */ + private static final int STATE_PLAYING = 2; + + final @WindowManager.TransitionType int mType; + private int mSyncId; + private @WindowManager.TransitionFlags int mFlags; + private final TransitionController mController; + final ArrayMap mParticipants = new ArrayMap<>(); + private int mState = STATE_COLLECTING; + private boolean mReadyCalled = false; + + Transition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags, TransitionController controller) { + mType = type; + mFlags = flags; + mController = controller; + mSyncId = mController.mSyncEngine.startSyncSet(this); + } + + /** + * Formally starts the transition. Participants can be collected before this is started, + * but this won't consider itself ready until started -- even if all the participants have + * drawn. + */ + void start() { + if (mState >= STATE_STARTED) { + Slog.w(TAG, "Transition already started: " + mSyncId); + } + mState = STATE_STARTED; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", + mSyncId); + if (mReadyCalled) { + setReady(); + } + } + + /** Adds wc to set of WindowContainers participating in this transition. */ + void collect(@NonNull WindowContainer wc) { + if (mSyncId < 0) return; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", + mSyncId, wc); + // Add to sync set before checking contains because it may not have added it at other + // times (eg. if wc was previously invisible). + mController.mSyncEngine.addToSyncSet(mSyncId, wc); + if (mParticipants.containsKey(wc)) return; + mParticipants.put(wc, new ChangeInfo()); + } + + /** + * Call this when all known changes related to this transition have been applied. Until + * all participants have finished drawing, the transition can still collect participants. + * + * If this is called before the transition is started, it will be deferred until start. + */ + void setReady() { + if (mSyncId < 0) return; + if (mState < STATE_STARTED) { + mReadyCalled = true; + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Finish collecting in transition %d", mSyncId); + mController.mSyncEngine.setReady(mSyncId); + mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal(); + } + + /** The transition has finished animating and is ready to finalize WM state */ + void finishTransition() { + if (mState < STATE_PLAYING) { + throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); + } + for (int i = 0; i < mParticipants.size(); ++i) { + final ActivityRecord ar = mParticipants.keyAt(i).asActivityRecord(); + if (ar == null || ar.mVisibleRequested) { + continue; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Commit activity becoming invisible: %s", ar); + ar.commitVisibility(false /* visible */, false /* performLayout */); + } + } + + @Override + public void onTransactionReady(int syncId, Set windowContainersReady) { + if (syncId != mSyncId) { + Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); + return; + } + mState = STATE_PLAYING; + mController.moveToPlaying(this); + final TransitionInfo info = calculateTransitionInfo(mType, mParticipants); + + SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); + int displayId = Display.DEFAULT_DISPLAY; + for (WindowContainer container : windowContainersReady) { + container.mergeBlastSyncTransaction(mergedTransaction); + displayId = container.mDisplayContent.getDisplayId(); + } + + handleNonAppWindowsInTransition(displayId, mType, mFlags); + + if (mController.getTransitionPlayer() != null) { + try { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Calling onTransitionReady: %s", info); + mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction); + } catch (RemoteException e) { + // If there's an exception when trying to send the mergedTransaction to the + // client, we should immediately apply it here so the transactions aren't lost. + mergedTransaction.apply(); + } + } else { + mergedTransaction.apply(); + } + mSyncId = -1; + } + + private void handleNonAppWindowsInTransition(int displayId, int transit, int flags) { + final DisplayContent dc = + mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + return; + } + if (transit == TRANSIT_KEYGUARD_GOING_AWAY) { + if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 + && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { + Animation anim = mController.mAtm.mWindowManager.mPolicy + .createKeyguardWallpaperExit( + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); + if (anim != null) { + anim.scaleCurrentDuration( + mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); + dc.mWallpaperController.startWallpaperAnimation(anim); + } + } + } + if (transit == TRANSIT_KEYGUARD_GOING_AWAY + || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { + dc.startKeyguardExitOnNonAppWindows( + transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, + (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); + mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(transit, 0); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append("TransitionRecord{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" id=" + mSyncId); + sb.append(" type=" + mType); + sb.append(" flags=" + mFlags); + sb.append('}'); + return sb.toString(); + } + + private static boolean reportIfNotTop(WindowContainer wc) { + // Organized tasks need to be reported anyways because Core won't show() their surfaces + // and we can't rely on onTaskAppeared because it isn't in sync. + // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. + return wc.isOrganized(); + } + + /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */ + private static int getChildDepth(WindowContainer child, WindowContainer ancestor) { + WindowContainer parent = child; + int depth = 0; + while (parent != null) { + if (parent == ancestor) { + return depth; + } + parent = parent.getParent(); + ++depth; + } + return -1; + } + + private static @TransitionInfo.TransitionMode int getModeFor(WindowContainer wc) { + if (wc.isVisibleRequested()) { + final Task t = wc.asTask(); + if (t != null && t.getHasBeenVisible()) { + return TransitionInfo.TRANSIT_SHOW; + } + return TransitionInfo.TRANSIT_OPEN; + } + return TransitionInfo.TRANSIT_CLOSE; + } + + /** + * Under some conditions (eg. all visible targets within a parent container are transitioning + * the same way) the transition can be "promoted" to the parent container. This means an + * animation can play just on the parent rather than all the individual children. + * + * @return {@code true} if transition in target can be promoted to its parent. + */ + private static boolean canPromote( + WindowContainer target, ArraySet topTargets) { + final WindowContainer parent = target.getParent(); + if (parent == null || !parent.canCreateRemoteAnimationTarget()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", + parent == null ? "no parent" : ("parent can't be target " + parent)); + return false; + } + @TransitionInfo.TransitionMode int mode = TransitionInfo.TRANSIT_NONE; + // Go through all siblings of this target to see if any of them would prevent + // the target from promoting. + siblingLoop: + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + final WindowContainer sibling = parent.getChildAt(i); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", + sibling); + // Check if any topTargets are the sibling or within it + for (int j = topTargets.size() - 1; j >= 0; --j) { + final int depth = getChildDepth(topTargets.valueAt(j), sibling); + if (depth < 0) continue; + if (depth == 0) { + final int siblingMode = sibling.isVisibleRequested() + ? TransitionInfo.TRANSIT_OPEN : TransitionInfo.TRANSIT_CLOSE; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " sibling is a top target with mode %s", + TransitionInfo.modeToString(siblingMode)); + if (mode == TransitionInfo.TRANSIT_NONE) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " no common mode yet, so set it"); + mode = siblingMode; + } else if (mode != siblingMode) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: common mode mismatch. was %s", + TransitionInfo.modeToString(mode)); + return false; + } + continue siblingLoop; + } else { + // Sibling subtree may not be promotable. + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: sibling contains top target %s", + topTargets.valueAt(j)); + return false; + } + } + // No other animations are playing in this sibling + if (sibling.isVisibleRequested()) { + // Sibling is visible but not animating, so no promote. + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: sibling is visible but not part of transition"); + return false; + } + } + return true; + } + + /** + * Go through topTargets and try to promote (see {@link #canPromote}) one of them. + * + * @param topTargets set of just the top-most targets in the hierarchy of participants. + * @param targets all targets that will be sent to the player. + * @return {@code true} if something was promoted. + */ + private static boolean tryPromote(ArraySet topTargets, + ArrayMap targets) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---"); + // Go through each target until we find one that can be promoted. + targetLoop: + for (WindowContainer targ : topTargets) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); + if (!canPromote(targ, topTargets)) { + continue; + } + final WindowContainer parent = targ.getParent(); + // No obstructions found to promotion, so promote + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " CAN PROMOTE: promoting to parent %s", parent); + final ChangeInfo parentInfo = new ChangeInfo(); + targets.put(parent, parentInfo); + // Go through all children of newly-promoted container and remove them from + // the top-targets. + for (int i = parent.getChildCount() - 1; i >= 0; --i) { + final WindowContainer child = parent.getChildAt(i); + int idx = targets.indexOfKey(child); + if (idx >= 0) { + if (reportIfNotTop(child)) { + targets.valueAt(idx).mParent = parent; + parentInfo.addChild(child); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " keep as target %s", child); + } else { + if (targets.valueAt(idx).mChildren != null) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " merging children in from %s: %s", child, + targets.valueAt(idx).mChildren); + parentInfo.addChildren(targets.valueAt(idx).mChildren); + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " remove from targets %s", child); + targets.removeAt(idx); + } + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " remove from topTargets %s", child); + topTargets.remove(child); + } + topTargets.add(parent); + return true; + } + return false; + } + + /** + * Find WindowContainers to be animated from a set of opening and closing apps. We will promote + * animation targets to higher level in the window hierarchy if possible. + */ + @VisibleForTesting + static TransitionInfo calculateTransitionInfo( + int type, Map participants) { + final TransitionInfo out = new TransitionInfo(type); + + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Start calculating TransitionInfo based on participants: %s", + new ArraySet<>(participants.keySet())); + + final ArraySet topTargets = new ArraySet<>(); + // The final animation targets which cannot promote to higher level anymore. + final ArrayMap targets = new ArrayMap<>(); + + final ArrayList tmpList = new ArrayList<>(); + + // Build initial set of top-level participants by removing any participants that are + // children of other participants or are otherwise invalid. + for (Map.Entry entry : participants.entrySet()) { + final WindowContainer wc = entry.getKey(); + // Don't include detached windows. + if (!wc.isAttached()) continue; + + final ChangeInfo changeInfo = entry.getValue(); + WindowContainer parent = wc.getParent(); + WindowContainer topParent = null; + // Keep track of always-report parents in bottom-to-top order + tmpList.clear(); + while (parent != null) { + if (participants.containsKey(parent)) { + topParent = parent; + } else if (reportIfNotTop(parent)) { + tmpList.add(parent); + } + parent = parent.getParent(); + } + if (topParent != null) { + // Add always-report parents along the way + parent = topParent; + for (int i = tmpList.size() - 1; i >= 0; --i) { + if (!participants.containsKey(tmpList.get(i))) { + final ChangeInfo info = new ChangeInfo(); + info.mParent = parent; + targets.put(tmpList.get(i), info); + } + parent = tmpList.get(i); + } + continue; + } + targets.put(wc, changeInfo); + topTargets.add(wc); + } + + // Populate children lists + for (int i = targets.size() - 1; i >= 0; --i) { + if (targets.valueAt(i).mParent != null) { + targets.get(targets.valueAt(i).mParent).addChild(targets.keyAt(i)); + } + } + + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " Initial targets: %s", new ArraySet<>(targets.keySet())); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets); + + // Combine targets by repeatedly going through the topTargets to see if they can be + // promoted until there aren't any promotions possible. + while (tryPromote(topTargets, targets)) { + // Empty on purpose + } + + // Convert all the resolved ChangeInfos into a TransactionInfo object. + for (int i = targets.size() - 1; i >= 0; --i) { + final WindowContainer target = targets.keyAt(i); + final ChangeInfo info = targets.valueAt(i); + final TransitionInfo.Change change = new TransitionInfo.Change( + target.mRemoteToken.toWindowContainerToken(), target.getSurfaceControl()); + if (info.mParent != null) { + change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); + } + change.setMode(getModeFor(target)); + out.addChange(change); + } + + return out; + } + + static Transition fromBinder(IBinder binder) { + return (Transition) binder; + } + + @VisibleForTesting + static class ChangeInfo { + WindowContainer mParent; + ArraySet mChildren; + // TODO(shell-transitions): other tracking like before state and bounds + void addChild(@NonNull WindowContainer wc) { + if (mChildren == null) { + mChildren = new ArraySet<>(); + } + mChildren.add(wc); + } + void addChildren(@NonNull ArraySet wcs) { + if (mChildren == null) { + mChildren = new ArraySet<>(); + } + mChildren.addAll(wcs); + } + } +} diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java new file mode 100644 index 0000000000000..d102c19bfff92 --- /dev/null +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2020 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.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; +import static android.view.WindowManager.TRANSIT_TASK_TO_BACK; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.WindowManager; +import android.window.ITransitionPlayer; + +import com.android.internal.protolog.ProtoLogGroup; +import com.android.internal.protolog.common.ProtoLog; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Handles all the aspects of recording and synchronizing transitions. + */ +class TransitionController { + private static final String TAG = "TransitionController"; + + private static final int[] SUPPORTED_LEGACY_TRANSIT_TYPES = {TRANSIT_TASK_OPEN, + TRANSIT_TASK_CLOSE, TRANSIT_TASK_TO_FRONT, TRANSIT_TASK_TO_BACK, + TRANSIT_TASK_OPEN_BEHIND, TRANSIT_KEYGUARD_GOING_AWAY}; + static { + Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES); + } + + final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine(); + private ITransitionPlayer mTransitionPlayer; + private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null; + final ActivityTaskManagerService mAtm; + + /** Currently playing transitions. When finished, records are removed from this list. */ + private final ArrayList mPlayingTransitions = new ArrayList<>(); + + /** + * The transition currently being constructed (collecting participants). + * TODO(shell-transitions): When simultaneous transitions are supported, merge this with + * mPlayingTransitions. + */ + private Transition mCollectingTransition = null; + + TransitionController(ActivityTaskManagerService atm) { + mAtm = atm; + } + + /** @see #createTransition(int, int) */ + @NonNull + Transition createTransition(int type) { + return createTransition(type, 0 /* flags */); + } + + /** + * Creates a transition. It can immediately collect participants. + */ + @NonNull + Transition createTransition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags) { + if (mCollectingTransition != null) { + throw new IllegalStateException("Simultaneous transitions not supported yet."); + } + mCollectingTransition = new Transition(type, flags, this); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", + mCollectingTransition); + return mCollectingTransition; + } + + void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + try { + if (mTransitionPlayer != null) { + mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); + mTransitionPlayer = null; + } + player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); + mTransitionPlayer = player; + } catch (RemoteException e) { + throw new RuntimeException("Unable to set transition player"); + } + } + + @Nullable ITransitionPlayer getTransitionPlayer() { + return mTransitionPlayer; + } + + boolean isShellTransitionsEnabled() { + return mTransitionPlayer != null; + } + + /** @return {@code true} if a transition is running */ + boolean inTransition() { + // TODO(shell-transitions): eventually properly support multiple + return mCollectingTransition != null || !mPlayingTransitions.isEmpty(); + } + + /** @return {@code true} if wc is in a participant subtree */ + boolean inTransition(@NonNull WindowContainer wc) { + if (mCollectingTransition != null && mCollectingTransition.mParticipants.containsKey(wc)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mPlayingTransitions.get(i).mParticipants.containsKey(p)) { + return true; + } + } + } + return false; + } + + /** + * Creates a transition and asks the TransitionPlayer (Shell) to start it. + * @return the created transition. Collection can start immediately. + */ + @NonNull + Transition requestTransition(@WindowManager.TransitionType int type) { + return requestTransition(type, 0 /* flags */); + } + + /** @see #requestTransition */ + @NonNull + Transition requestTransition(@WindowManager.TransitionType int type, + @WindowManager.TransitionFlags int flags) { + if (mTransitionPlayer == null) { + throw new IllegalStateException("Shell Transitions not enabled"); + } + final Transition transition = createTransition(type, flags); + try { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Requesting StartTransition: %s", transition); + mTransitionPlayer.requestStartTransition(type, transition); + } catch (RemoteException e) { + Slog.e(TAG, "Error requesting transition", e); + transition.start(); + } + return transition; + } + + /** + * Temporary adapter that converts the legacy AppTransition's prepareAppTransition call into + * a Shell transition request. If shell transitions are enabled, this will take priority in + * handling transition types that it supports. All other transitions will be ignored and thus + * be handled by the legacy apptransition system. This allows both worlds to live in tandem + * during migration. + * + * @return {@code true} if the transition is handled. + */ + boolean adaptLegacyPrepare(@WindowManager.TransitionType int transit, + @WindowManager.TransitionFlags int flags, boolean forceOverride) { + if (!isShellTransitionsEnabled() + || Arrays.binarySearch(SUPPORTED_LEGACY_TRANSIT_TYPES, transit) < 0) { + return false; + } + if (inTransition()) { + if (AppTransition.isKeyguardTransit(transit)) { + // TODO(shell-transitions): add to flags + } else if (forceOverride) { + // TODO(shell-transitions): sort out these flags + } else if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) { + // TODO(shell-transitions): record crashing + } + } else { + requestTransition(transit, flags); + } + return true; + } + + /** @see Transition#collect */ + void collect(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.collect(wc); + } + + /** @see Transition#setReady */ + void setReady() { + if (mCollectingTransition == null) return; + mCollectingTransition.setReady(); + } + + /** @see Transition#finishTransition */ + void finishTransition(@NonNull IBinder token) { + final Transition record = Transition.fromBinder(token); + if (record == null || !mPlayingTransitions.contains(record)) { + Slog.e(TAG, "Trying to finish a non-playing transition " + token); + return; + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); + mPlayingTransitions.remove(record); + record.finishTransition(); + } + + void moveToPlaying(Transition transition) { + if (transition != mCollectingTransition) { + throw new IllegalStateException("Trying to move non-collecting transition to playing"); + } + mCollectingTransition = null; + mPlayingTransitions.add(transition); + } + +} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2b93080a8dad8..2ece30d24b3a3 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -417,7 +417,9 @@ class WindowContainer extends ConfigurationContainer< void setInitialSurfaceControlProperties(SurfaceControl.Builder b) { setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build()); - getSyncTransaction().show(mSurfaceControl); + if (showSurfaceOnCreation()) { + getSyncTransaction().show(mSurfaceControl); + } onSurfaceShown(getSyncTransaction()); updateSurfacePositionNonOrganized(); } @@ -998,6 +1000,21 @@ class WindowContainer extends ConfigurationContainer< return false; } + /** + * Is this window's surface needed? This is almost like isVisible, except when participating + * in a transition, this will reflect the final visibility while isVisible won't change until + * the transition is finished. + */ + boolean isVisibleRequested() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + if (child.isVisibleRequested()) { + return true; + } + } + return false; + } + /** * Called when the visibility of a child is asked to change. This is before visibility actually * changes (eg. a transition animation might play out first). @@ -2816,6 +2833,13 @@ class WindowContainer extends ConfigurationContainer< return false; } + /** + * @return {@code true} if this container's surface should be shown when it is created. + */ + boolean showSurfaceOnCreation() { + return true; + } + static WindowContainer fromBinder(IBinder binder) { return RemoteToken.fromBinder(binder).getContainer(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index d434bf9d3816a..2ea854ad3b655 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2460,7 +2460,10 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { + if (mAtmService.getTransitionController().inTransition(win)) { + focusMayChange = true; + win.mAnimatingExit = true; + } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { focusMayChange = true; win.mAnimatingExit = true; } else if (win.isAnimating(TRANSITION | PARENTS)) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 999181dc486cd..f1641cdfcf67e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -26,6 +26,8 @@ import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -40,6 +42,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; import android.window.WindowContainerToken; @@ -88,21 +91,85 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final TaskOrganizerController mTaskOrganizerController; final DisplayAreaOrganizerController mDisplayAreaOrganizerController; + final TransitionController mTransitionController; + WindowOrganizerController(ActivityTaskManagerService atm) { mService = atm; mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); + mTransitionController = new TransitionController(atm); + } + + TransitionController getTransitionController() { + return mTransitionController; } @Override public void applyTransaction(WindowContainerTransaction t) { - applySyncTransaction(t, null /*callback*/); + applyTransaction(t, null /*callback*/, null /*transition*/); } @Override public int applySyncTransaction(WindowContainerTransaction t, IWindowContainerTransactionCallback callback) { + return applyTransaction(t, callback, null /*transition*/); + } + + @Override + public IBinder startTransition(int type, @Nullable IBinder transitionToken, + @Nullable WindowContainerTransaction t) { + enforceStackPermission("startTransition()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + Transition transition = Transition.fromBinder(transitionToken); + if (transition == null) { + if (type < 0) { + throw new IllegalArgumentException("Can't create transition with no type"); + } + transition = mTransitionController.createTransition(type); + } + transition.start(); + if (t == null) { + t = new WindowContainerTransaction(); + } + applyTransaction(t, null /*callback*/, transition); + return transition; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public int finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t, + @Nullable IWindowContainerTransactionCallback callback) { + enforceStackPermission("finishTransition()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + int syncId = -1; + if (t != null) { + syncId = applyTransaction(t, callback, null /*transition*/); + } + getTransitionController().finishTransition(transitionToken); + return syncId; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * @param callback If non-null, this will be a sync-transaction. + * @param transition A transition to collect changes into. + * @return a BLAST sync-id if this is a non-transition, sync transaction. + */ + private int applyTransaction(@NonNull WindowContainerTransaction t, + @Nullable IWindowContainerTransactionCallback callback, + @Nullable Transition transition) { enforceStackPermission("applySyncTransaction()"); int syncId = -1; if (t == null) { @@ -152,6 +219,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } int containerEffect = applyWindowContainerChange(wc, entry.getValue()); + if (transition != null) transition.collect(wc); effects |= containerEffect; // Lifecycle changes will trigger ensureConfig for everything. @@ -173,6 +241,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub addToSyncSet(syncId, wc); } effects |= sanitizeAndApplyHierarchyOp(wc, hop); + if (transition != null) { + transition.collect(wc); + if (hop.isReparent() && hop.getNewParent() != null) { + transition.collect(WindowContainer.fromBinder(hop.getNewParent())); + } + } } // Queue-up bounds-change transactions for tasks which are now organized. Do // this after hierarchy ops so we have the final organized state. @@ -512,6 +586,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return true; } + @Override + public void registerTransitionPlayer(ITransitionPlayer player) { + enforceStackPermission("registerTransitionPlayer()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mTransitionController.registerTransitionPlayer(player); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void enforceStackPermission(String func) { mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ad2817720387c..e38ad291e54c0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1683,6 +1683,11 @@ class WindowState extends WindowContainer implements WindowManagerP || mControllableInsetProvider.isClientVisible()); } + @Override + boolean isVisibleRequested() { + return isVisible(); + } + /** * Ensures that all the policy visibility bits are set. * @return {@code true} if all flags about visiblity are set @@ -1771,7 +1776,7 @@ class WindowState extends WindowContainer implements WindowManagerP } final ActivityRecord atoken = mActivityRecord; if (atoken != null) { - return ((!isParentWindowHidden() && atoken.mVisibleRequested) + return ((!isParentWindowHidden() && atoken.isVisible()) || isAnimating(TRANSITION | PARENTS)); } return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java new file mode 100644 index 0000000000000..ce22205c75f0e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 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_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.window.ITaskOrganizer; +import android.window.TransitionInfo; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest WmTests:TransitionRecordTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TransitionTests extends WindowTestsBase { + + @Test + public void testCreateInfo_NewTask() { + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + newTask.setHasBeenVisible(true); + oldTask.setHasBeenVisible(false); + final ActivityRecord closing = createActivityRecordInTask(oldTask); + final ActivityRecord opening = createActivityRecordInTask(newTask); + closing.setVisible(true); + closing.mVisibleRequested = false; + opening.setVisible(false); + opening.mVisibleRequested = true; + ArrayMap participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check basic both tasks participating + participants.put(oldTask, new Transition.ChangeInfo()); + participants.put(newTask, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertEquals(transitType, info.getType()); + + // Check that children are pruned + participants.put(opening, new Transition.ChangeInfo()); + participants.put(closing, new Transition.ChangeInfo()); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check combined prune and promote + participants.remove(newTask); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check multi promote + participants.remove(oldTask); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + } + + @Test + public void testCreateInfo_NestedTasks() { + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task newNestedTask = createTaskInStack(newTask, 0); + final Task newNestedTask2 = createTaskInStack(newTask, 0); + final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + newTask.setHasBeenVisible(true); + oldTask.setHasBeenVisible(false); + final ActivityRecord closing = createActivityRecordInTask(oldTask); + final ActivityRecord opening = createActivityRecordInTask(newNestedTask); + final ActivityRecord opening2 = createActivityRecordInTask(newNestedTask2); + closing.setVisible(true); + closing.mVisibleRequested = false; + opening.setVisible(false); + opening.mVisibleRequested = true; + opening2.setVisible(false); + opening2.mVisibleRequested = true; + ArrayMap participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check full promotion from leaf + participants.put(oldTask, new Transition.ChangeInfo()); + participants.put(opening, new Transition.ChangeInfo()); + participants.put(opening2, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertEquals(transitType, info.getType()); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + + // Check that unchanging but visible descendant of sibling prevents promotion + participants.remove(opening2); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); + } + + @Test + public void testCreateInfo_DisplayArea() { + final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task showNestedTask = createTaskInStack(showTask, 0); + final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final DisplayArea tda = showTask.getDisplayArea(); + showTask.setHasBeenVisible(true); + showTask2.setHasBeenVisible(true); + final ActivityRecord showing = createActivityRecordInTask(showNestedTask); + final ActivityRecord showing2 = createActivityRecordInTask(showTask2); + showing.setVisible(false); + showing.mVisibleRequested = true; + showing2.setVisible(false); + showing2.mVisibleRequested = true; + ArrayMap participants = new ArrayMap<>(); + + int transitType = TRANSIT_TASK_OPEN; + + // Check promotion to DisplayArea + participants.put(showing, new Transition.ChangeInfo()); + participants.put(showing2, new Transition.ChangeInfo()); + TransitionInfo info = + Transition.calculateTransitionInfo(transitType, participants); + assertEquals(1, info.getChanges().size()); + assertEquals(transitType, info.getType()); + assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); + + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + // Check that organized tasks get reported even if not top + showTask.mTaskOrganizer = mockOrg; + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); + assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken())); + // Even if DisplayArea explicitly participating + participants.put(tda, new Transition.ChangeInfo()); + info = Transition.calculateTransitionInfo(transitType, participants); + assertEquals(2, info.getChanges().size()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 986807e661f12..6237be0f4b268 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -265,6 +265,11 @@ class WindowTestsBase extends SystemServiceTestsBase { return activity; } + /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */ + static ActivityRecord createActivityRecordInTask(Task task) { + return createActivityRecordInTask(task.getDisplayContent(), task); + } + static ActivityRecord createTestActivityRecord(DisplayContent dc) { final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build(); postCreateActivitySetup(activity, dc);