Loading core/java/android/view/WindowManager.java +11 −0 Original line number Diff line number Diff line Loading @@ -386,6 +386,16 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_KEYGUARD_UNOCCLUDE = 9; /** * The first slot for custom transition types. Callers (like Shell) can make use of custom * transition types for dealing with special cases. These types are effectively ignored by * Core and will just be passed along as part of TransitionInfo objects. An example is * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type, * Shell can properly dispatch the results of that transition to the split-screen * implementation. * @hide */ int TRANSIT_FIRST_CUSTOM = 10; /** * @hide Loading @@ -401,6 +411,7 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_GOING_AWAY, TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) @interface TransitionType {} Loading core/java/android/window/ITransitionPlayer.aidl +5 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.window; import android.app.ActivityManager; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; Loading Loading @@ -58,6 +59,9 @@ oneway interface ITransitionPlayer { * @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}. * @param triggerTask If non-null, the task containing the activity whose lifecycle change * (start or finish) has caused this transition to occur. */ void requestStartTransition(int type, in IBinder transitionToken); void requestStartTransition(int type, in IBinder transitionToken, in ActivityManager.RunningTaskInfo triggerTask); } core/java/android/window/TransitionInfo.java +21 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; Loading Loading @@ -153,7 +154,8 @@ public final class TransitionInfo implements Parcelable { /** * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing * participants to animate within. This will generally be placed at the highest-z-order * shared ancestor of all participants. * shared ancestor of all participants. While this is non-null, it's possible for the rootleash * to be invalid if the transition is a no-op. */ @NonNull public SurfaceControl getRootLeash() { Loading Loading @@ -181,7 +183,7 @@ public final class TransitionInfo implements Parcelable { @Nullable public Change getChange(@NonNull WindowContainerToken token) { for (int i = mChanges.size() - 1; i >= 0; --i) { if (mChanges.get(i).mContainer == token) { if (token.equals(mChanges.get(i).mContainer)) { return mChanges.get(i); } } Loading Loading @@ -254,6 +256,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; Loading @@ -270,6 +273,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); } /** Sets the parent of this change's container. The parent must be a participant or null. */ Loading Loading @@ -302,6 +306,14 @@ public final class TransitionInfo implements Parcelable { mEndRelOffset.set(left, top); } /** * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. */ public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) { mTaskInfo = taskInfo; } /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { Loading Loading @@ -359,6 +371,12 @@ public final class TransitionInfo implements Parcelable { return mLeash; } /** @return the task info or null if this isn't a task */ @NonNull public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } @Override /** @hide */ public void writeToParcel(@NonNull Parcel dest, int flags) { Loading @@ -370,6 +388,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); } @NonNull Loading libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +4 −7 Original line number Diff line number Diff line Loading @@ -55,19 +55,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); mLeashByTaskId.put(taskInfo.taskId, leash); if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final Point positionInParent = taskInfo.positionInParent; mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). t.setWindowCrop(leash, null); t.setPosition(leash, positionInParent.x, positionInParent.y); // 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); } }); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java +166 −35 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; Loading @@ -25,15 +26,17 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.os.IBinder; import android.os.RemoteException; 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.WindowContainerTransaction; import android.window.WindowOrganizer; import androidx.annotation.BinderThread; Loading @@ -59,8 +62,16 @@ public class Transitions { private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); private static final class ActiveTransition { ArrayList<Animator> mAnimations = null; TransitionHandler mFirstHandler = null; } /** Keeps track of currently tracked transitions and all the animations associated with each */ private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>(); private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { Loading @@ -75,6 +86,22 @@ public class Transitions { taskOrganizer.registerTransitionPlayer(mPlayerImpl); } /** * Adds a handler candidate. * @see TransitionHandler */ public void addHandler(@NonNull TransitionHandler handler) { mHandlers.add(handler); } public ShellExecutor getMainExecutor() { return mMainExecutor; } public ShellExecutor getAnimExecutor() { return mAnimExecutor; } // TODO(shell-transitions): real animations private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash, boolean show) { Loading @@ -93,7 +120,7 @@ public class Transitions { transaction.apply(); mTransactionPool.release(transaction); mMainExecutor.execute(() -> { mActiveTransitions.get(transition).remove(va); mActiveTransitions.get(transition).mAnimations.remove(va); onFinish(transition); }); }; Loading @@ -114,30 +141,23 @@ public class Transitions { @Override public void onAnimationRepeat(Animator animation) { } }); mActiveTransitions.get(transition).add(va); mActiveTransitions.get(transition).mAnimations.add(va); mAnimExecutor.execute(va::start); } private static boolean isOpeningType(@WindowManager.TransitionType int type) { /** @return true if the transition was triggered by opening something vs closing something */ public static boolean isOpeningType(@WindowManager.TransitionType int type) { return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; } private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, /** * Reparents all participants into a shared parent and orders them based on: the global transit * type, their transit mode, and their destination z-order. */ private static void setupStartState(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); // start task 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<>()); boolean isOpening = isOpeningType(info.getType()); if (info.getRootLeash().isValid()) { t.show(info.getRootLeash()); Loading @@ -148,24 +168,26 @@ public class Transitions { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); // Don't animate anything with an animating parent // Don't move anything with an animating parent if (change.getParent() != null) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); t.setAlpha(leash, 1.f); t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); } continue; } t.reparent(leash, info.getRootLeash()); t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x, change.getEndAbsBounds().top - info.getRootOffset().y); t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, change.getStartAbsBounds().top - info.getRootOffset().y); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); if (isOpening) { // put on top and fade in // put on top with 0 alpha t.setLayer(leash, info.getChanges().size() - i); if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { // This received a transferred starting window, so make it immediately Loading @@ -173,47 +195,155 @@ public class Transitions { t.setAlpha(leash, 1.f); } else { t.setAlpha(leash, 0.f); startExampleAnimation(transitionToken, leash, true /* show */); } } else { // put on bottom and leave it visible without fade // put on bottom and leave it visible t.setLayer(leash, -i); t.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { // put on bottom and leave visible without fade // put on bottom and leave visible t.setLayer(leash, -i); } else { // put on top and fade out // put on top t.setLayer(leash, info.getChanges().size() - i); startExampleAnimation(transitionToken, leash, false /* show */); } } else { } else { // CHANGE t.setLayer(leash, info.getChanges().size() - i); } } } private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); final ActiveTransition active = mActiveTransitions.get(transitionToken); if (active == null) { throw new IllegalStateException("Got transitionReady for non-active transition " + transitionToken + ". expecting one of " + mActiveTransitions.keySet()); } if (active.mAnimations != null) { throw new IllegalStateException("Got a duplicate onTransitionReady call for " + transitionToken); } if (!info.getRootLeash().isValid()) { // Invalid root-leash implies that the transition is empty/no-op, so just do // housekeeping and return. t.apply(); onFinish(transitionToken); return; } setupStartState(info, t); final Runnable finishRunnable = () -> onFinish(transitionToken); // If a handler chose to uniquely run this animation, try delegating to it. if (active.mFirstHandler != null && active.mFirstHandler.startAnimation( transitionToken, info, t, finishRunnable)) { return; } // Otherwise give every other handler a chance (in order) for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == active.mFirstHandler) continue; if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) { return; } } // No handler chose to perform this animation, so fall-back to the // default animation handling. final boolean isOpening = isOpeningType(info.getType()); active.mAnimations = new ArrayList<>(); // Play fade animations for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); // Don't animate anything with an animating parent if (change.getParent() != null) continue; final int mode = info.getChanges().get(i).getMode(); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { // This received a transferred starting window, so don't animate continue; } // fade in startExampleAnimation(transitionToken, change.getLeash(), true /* show */); } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { // fade out startExampleAnimation(transitionToken, change.getLeash(), false /* show */); } } t.apply(); onFinish(transitionToken); } private void onFinish(IBinder transition) { if (!mActiveTransitions.get(transition).isEmpty()) return; final ActiveTransition active = mActiveTransitions.get(transition); if (active.mAnimations != null && !active.mAnimations.isEmpty()) return; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animations finished, notifying core %s", transition); mActiveTransitions.remove(transition); mOrganizer.finishTransition(transition, null, null); } private void requestStartTransition(int type, @NonNull IBinder transitionToken) { private void requestStartTransition(int type, @NonNull IBinder transitionToken, @Nullable ActivityManager.RunningTaskInfo triggerTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s", type, transitionToken); if (mActiveTransitions.containsKey(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); } IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */); mActiveTransitions.put(transition, null); final ActiveTransition active = new ActiveTransition(); WindowContainerTransaction wct = null; for (int i = mHandlers.size() - 1; i >= 0; --i) { wct = mHandlers.get(i).handleRequest(type, transitionToken, triggerTask); if (wct != null) { active.mFirstHandler = mHandlers.get(i); break; } } IBinder transition = mOrganizer.startTransition(type, transitionToken, wct); mActiveTransitions.put(transition, active); } /** Start a new transition directly. */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { final ActiveTransition active = new ActiveTransition(); active.mFirstHandler = handler; IBinder transition = mOrganizer.startTransition(type, null /* token */, wct); mActiveTransitions.put(transition, active); return transition; } /** * Interface for something which can handle a subset of transitions. */ public interface TransitionHandler { /** * Starts a transition animation. This is always called if handleRequest returned non-null * for a particular transition. Otherwise, it is only called if no other handler before * it handled the transition. * * @return true if transition was handled, false if not (falls-back to default). */ boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback); /** * Potentially handles a startTransition request. * @param type The transition type * @param triggerTask The task which triggered this transition request. * @return WCT to apply with transition-start or null if this handler isn't handling * the request. */ @Nullable WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type, @NonNull IBinder transition, @Nullable ActivityManager.RunningTaskInfo triggerTask); } @BinderThread Loading @@ -227,9 +357,10 @@ public class Transitions { } @Override public void requestStartTransition(int i, IBinder iBinder) throws RemoteException { public void requestStartTransition(int i, IBinder iBinder, ActivityManager.RunningTaskInfo runningTaskInfo) throws RemoteException { mMainExecutor.execute(() -> { Transitions.this.requestStartTransition(i, iBinder); Transitions.this.requestStartTransition(i, iBinder, runningTaskInfo); }); } } Loading Loading
core/java/android/view/WindowManager.java +11 −0 Original line number Diff line number Diff line Loading @@ -386,6 +386,16 @@ public interface WindowManager extends ViewManager { * @hide */ int TRANSIT_KEYGUARD_UNOCCLUDE = 9; /** * The first slot for custom transition types. Callers (like Shell) can make use of custom * transition types for dealing with special cases. These types are effectively ignored by * Core and will just be passed along as part of TransitionInfo objects. An example is * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type, * Shell can properly dispatch the results of that transition to the split-screen * implementation. * @hide */ int TRANSIT_FIRST_CUSTOM = 10; /** * @hide Loading @@ -401,6 +411,7 @@ public interface WindowManager extends ViewManager { TRANSIT_KEYGUARD_GOING_AWAY, TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FIRST_CUSTOM }) @Retention(RetentionPolicy.SOURCE) @interface TransitionType {} Loading
core/java/android/window/ITransitionPlayer.aidl +5 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.window; import android.app.ActivityManager; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; Loading Loading @@ -58,6 +59,9 @@ oneway interface ITransitionPlayer { * @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}. * @param triggerTask If non-null, the task containing the activity whose lifecycle change * (start or finish) has caused this transition to occur. */ void requestStartTransition(int type, in IBinder transitionToken); void requestStartTransition(int type, in IBinder transitionToken, in ActivityManager.RunningTaskInfo triggerTask); }
core/java/android/window/TransitionInfo.java +21 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; Loading Loading @@ -153,7 +154,8 @@ public final class TransitionInfo implements Parcelable { /** * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing * participants to animate within. This will generally be placed at the highest-z-order * shared ancestor of all participants. * shared ancestor of all participants. While this is non-null, it's possible for the rootleash * to be invalid if the transition is a no-op. */ @NonNull public SurfaceControl getRootLeash() { Loading Loading @@ -181,7 +183,7 @@ public final class TransitionInfo implements Parcelable { @Nullable public Change getChange(@NonNull WindowContainerToken token) { for (int i = mChanges.size() - 1; i >= 0; --i) { if (mChanges.get(i).mContainer == token) { if (token.equals(mChanges.get(i).mContainer)) { return mChanges.get(i); } } Loading Loading @@ -254,6 +256,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mStartAbsBounds = new Rect(); private final Rect mEndAbsBounds = new Rect(); private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; Loading @@ -270,6 +273,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.readFromParcel(in); mEndAbsBounds.readFromParcel(in); mEndRelOffset.readFromParcel(in); mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); } /** Sets the parent of this change's container. The parent must be a participant or null. */ Loading Loading @@ -302,6 +306,14 @@ public final class TransitionInfo implements Parcelable { mEndRelOffset.set(left, top); } /** * Sets the taskinfo of this container if this is a task. WARNING: this takes the * reference, so don't modify it afterwards. */ public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) { mTaskInfo = taskInfo; } /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { Loading Loading @@ -359,6 +371,12 @@ public final class TransitionInfo implements Parcelable { return mLeash; } /** @return the task info or null if this isn't a task */ @NonNull public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } @Override /** @hide */ public void writeToParcel(@NonNull Parcel dest, int flags) { Loading @@ -370,6 +388,7 @@ public final class TransitionInfo implements Parcelable { mStartAbsBounds.writeToParcel(dest, flags); mEndAbsBounds.writeToParcel(dest, flags); mEndRelOffset.writeToParcel(dest, flags); dest.writeTypedObject(mTaskInfo, flags); } @NonNull Loading
libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +4 −7 Original line number Diff line number Diff line Loading @@ -55,19 +55,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); mLeashByTaskId.put(taskInfo.taskId, leash); if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final Point positionInParent = taskInfo.positionInParent; mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). t.setWindowCrop(leash, null); t.setPosition(leash, positionInParent.x, positionInParent.y); // 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); } }); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java +166 −35 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; Loading @@ -25,15 +26,17 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.os.IBinder; import android.os.RemoteException; 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.WindowContainerTransaction; import android.window.WindowOrganizer; import androidx.annotation.BinderThread; Loading @@ -59,8 +62,16 @@ public class Transitions { private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); private static final class ActiveTransition { ArrayList<Animator> mAnimations = null; TransitionHandler mFirstHandler = null; } /** Keeps track of currently tracked transitions and all the animations associated with each */ private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>(); private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { Loading @@ -75,6 +86,22 @@ public class Transitions { taskOrganizer.registerTransitionPlayer(mPlayerImpl); } /** * Adds a handler candidate. * @see TransitionHandler */ public void addHandler(@NonNull TransitionHandler handler) { mHandlers.add(handler); } public ShellExecutor getMainExecutor() { return mMainExecutor; } public ShellExecutor getAnimExecutor() { return mAnimExecutor; } // TODO(shell-transitions): real animations private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash, boolean show) { Loading @@ -93,7 +120,7 @@ public class Transitions { transaction.apply(); mTransactionPool.release(transaction); mMainExecutor.execute(() -> { mActiveTransitions.get(transition).remove(va); mActiveTransitions.get(transition).mAnimations.remove(va); onFinish(transition); }); }; Loading @@ -114,30 +141,23 @@ public class Transitions { @Override public void onAnimationRepeat(Animator animation) { } }); mActiveTransitions.get(transition).add(va); mActiveTransitions.get(transition).mAnimations.add(va); mAnimExecutor.execute(va::start); } private static boolean isOpeningType(@WindowManager.TransitionType int type) { /** @return true if the transition was triggered by opening something vs closing something */ public static boolean isOpeningType(@WindowManager.TransitionType int type) { return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; } private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, /** * Reparents all participants into a shared parent and orders them based on: the global transit * type, their transit mode, and their destination z-order. */ private static void setupStartState(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); // start task 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<>()); boolean isOpening = isOpeningType(info.getType()); if (info.getRootLeash().isValid()) { t.show(info.getRootLeash()); Loading @@ -148,24 +168,26 @@ public class Transitions { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); // Don't animate anything with an animating parent // Don't move anything with an animating parent if (change.getParent() != null) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); t.setAlpha(leash, 1.f); t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); } continue; } t.reparent(leash, info.getRootLeash()); t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x, change.getEndAbsBounds().top - info.getRootOffset().y); t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, change.getStartAbsBounds().top - info.getRootOffset().y); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); if (isOpening) { // put on top and fade in // put on top with 0 alpha t.setLayer(leash, info.getChanges().size() - i); if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { // This received a transferred starting window, so make it immediately Loading @@ -173,47 +195,155 @@ public class Transitions { t.setAlpha(leash, 1.f); } else { t.setAlpha(leash, 0.f); startExampleAnimation(transitionToken, leash, true /* show */); } } else { // put on bottom and leave it visible without fade // put on bottom and leave it visible t.setLayer(leash, -i); t.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { // put on bottom and leave visible without fade // put on bottom and leave visible t.setLayer(leash, -i); } else { // put on top and fade out // put on top t.setLayer(leash, info.getChanges().size() - i); startExampleAnimation(transitionToken, leash, false /* show */); } } else { } else { // CHANGE t.setLayer(leash, info.getChanges().size() - i); } } } private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); final ActiveTransition active = mActiveTransitions.get(transitionToken); if (active == null) { throw new IllegalStateException("Got transitionReady for non-active transition " + transitionToken + ". expecting one of " + mActiveTransitions.keySet()); } if (active.mAnimations != null) { throw new IllegalStateException("Got a duplicate onTransitionReady call for " + transitionToken); } if (!info.getRootLeash().isValid()) { // Invalid root-leash implies that the transition is empty/no-op, so just do // housekeeping and return. t.apply(); onFinish(transitionToken); return; } setupStartState(info, t); final Runnable finishRunnable = () -> onFinish(transitionToken); // If a handler chose to uniquely run this animation, try delegating to it. if (active.mFirstHandler != null && active.mFirstHandler.startAnimation( transitionToken, info, t, finishRunnable)) { return; } // Otherwise give every other handler a chance (in order) for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == active.mFirstHandler) continue; if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) { return; } } // No handler chose to perform this animation, so fall-back to the // default animation handling. final boolean isOpening = isOpeningType(info.getType()); active.mAnimations = new ArrayList<>(); // Play fade animations for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); // Don't animate anything with an animating parent if (change.getParent() != null) continue; final int mode = info.getChanges().get(i).getMode(); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { // This received a transferred starting window, so don't animate continue; } // fade in startExampleAnimation(transitionToken, change.getLeash(), true /* show */); } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { // fade out startExampleAnimation(transitionToken, change.getLeash(), false /* show */); } } t.apply(); onFinish(transitionToken); } private void onFinish(IBinder transition) { if (!mActiveTransitions.get(transition).isEmpty()) return; final ActiveTransition active = mActiveTransitions.get(transition); if (active.mAnimations != null && !active.mAnimations.isEmpty()) return; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animations finished, notifying core %s", transition); mActiveTransitions.remove(transition); mOrganizer.finishTransition(transition, null, null); } private void requestStartTransition(int type, @NonNull IBinder transitionToken) { private void requestStartTransition(int type, @NonNull IBinder transitionToken, @Nullable ActivityManager.RunningTaskInfo triggerTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s", type, transitionToken); if (mActiveTransitions.containsKey(transitionToken)) { throw new RuntimeException("Transition already started " + transitionToken); } IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */); mActiveTransitions.put(transition, null); final ActiveTransition active = new ActiveTransition(); WindowContainerTransaction wct = null; for (int i = mHandlers.size() - 1; i >= 0; --i) { wct = mHandlers.get(i).handleRequest(type, transitionToken, triggerTask); if (wct != null) { active.mFirstHandler = mHandlers.get(i); break; } } IBinder transition = mOrganizer.startTransition(type, transitionToken, wct); mActiveTransitions.put(transition, active); } /** Start a new transition directly. */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { final ActiveTransition active = new ActiveTransition(); active.mFirstHandler = handler; IBinder transition = mOrganizer.startTransition(type, null /* token */, wct); mActiveTransitions.put(transition, active); return transition; } /** * Interface for something which can handle a subset of transitions. */ public interface TransitionHandler { /** * Starts a transition animation. This is always called if handleRequest returned non-null * for a particular transition. Otherwise, it is only called if no other handler before * it handled the transition. * * @return true if transition was handled, false if not (falls-back to default). */ boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback); /** * Potentially handles a startTransition request. * @param type The transition type * @param triggerTask The task which triggered this transition request. * @return WCT to apply with transition-start or null if this handler isn't handling * the request. */ @Nullable WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type, @NonNull IBinder transition, @Nullable ActivityManager.RunningTaskInfo triggerTask); } @BinderThread Loading @@ -227,9 +357,10 @@ public class Transitions { } @Override public void requestStartTransition(int i, IBinder iBinder) throws RemoteException { public void requestStartTransition(int i, IBinder iBinder, ActivityManager.RunningTaskInfo runningTaskInfo) throws RemoteException { mMainExecutor.execute(() -> { Transitions.this.requestStartTransition(i, iBinder); Transitions.this.requestStartTransition(i, iBinder, runningTaskInfo); }); } } Loading