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:
+ *
+ * - Core starts an activity and calls {@link #requestStartTransition}
+ *
- 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).
+ *
- Once all collected changes on the transition have finished drawing, Core will then call
+ * {@link #onTransitionReady} here to delegate the actual animation.
+ *
- 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 f237598cb5972..19cfcb21c8ac0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2462,7 +2462,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 89a8cb712e984..8f42b3f154f71 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);