From 68fbb4725016b6fd286a4ed04e87a07aee9ce750 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Tue, 10 Nov 2020 17:08:27 -0800 Subject: [PATCH] Basic support for split-screen in new transition system Sends the "trigger" taskinfo associated with a transition when applicable. This can be used by the transitionplayer to make decisions about how to start and play transitions. Additionally, TaskInfo is provided in each Change object if they are tasks. This provides all the information needed to choose how to handle split. Shell's transitionplayer makes use of this to add support for "handlers" to deal with specific transition requests. Currently a single handler is added to process split-screen transitions. Bug: 169035082 Test: Use split-screen (enter, exit via swipe up/down, exit via dismiss top) Change-Id: I53a03dd85cb260505e66d5d87a34afa4ab1f8cc8 --- core/java/android/view/WindowManager.java | 11 + .../android/window/ITransitionPlayer.aidl | 6 +- core/java/android/window/TransitionInfo.java | 23 +- .../wm/shell/FullscreenTaskListener.java | 11 +- .../src/com/android/wm/shell/Transitions.java | 201 ++++++++-- .../LegacySplitScreenController.java | 34 +- .../LegacySplitScreenTaskListener.java | 24 +- .../SplitScreenTransitions.java | 342 ++++++++++++++++++ .../legacysplitscreen/WindowManagerProxy.java | 97 +++-- .../systemui/dagger/SysUIComponent.java | 2 +- .../android/systemui/dagger/WMComponent.java | 2 +- .../recents/OverviewProxyService.java | 2 +- .../systemui/wmshell/TvWMShellModule.java | 6 +- .../systemui/wmshell/WMShellModule.java | 10 +- .../recents/OverviewProxyServiceTest.java | 2 +- .../android/systemui/wmshell/WMShellTest.java | 2 +- .../android/server/wm/ActivityStarter.java | 3 +- .../server/wm/ActivityTaskSupervisor.java | 1 + .../com/android/server/wm/Transition.java | 12 + .../server/wm/TransitionController.java | 13 +- .../server/wm/WindowOrganizerController.java | 32 +- .../android/server/wm/TransitionTests.java | 12 + 22 files changed, 738 insertions(+), 110 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6d88d637dc241..482f2f07499d4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -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 @@ -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 {} diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl index a8a29b26a1485..55d47cb7ed8bf 100644 --- a/core/java/android/window/ITransitionPlayer.aidl +++ b/core/java/android/window/ITransitionPlayer.aidl @@ -16,6 +16,7 @@ package android.window; +import android.app.ActivityManager; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; @@ -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); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index da291cf0fd2cf..d1d49b6d47221 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -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; @@ -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() { @@ -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); } } @@ -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; @@ -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. */ @@ -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() { @@ -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) { @@ -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 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 63d31182a7485..8817f8aaec7c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -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); - } + 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/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java index 54863d23643a2..5213f6c80989c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java @@ -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; @@ -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; @@ -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 mHandlers = new ArrayList<>(); + + private static final class ActiveTransition { + ArrayList mAnimations = null; + TransitionHandler mFirstHandler = null; + } + /** Keeps track of currently tracked transitions and all the animations associated with each */ - private final ArrayMap> mActiveTransitions = new ArrayMap<>(); + private final ArrayMap mActiveTransitions = new ArrayMap<>(); public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { @@ -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) { @@ -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); }); }; @@ -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()); @@ -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 @@ -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 @@ -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); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java index 6b79a3661bc07..789bd1a90731f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java @@ -44,6 +44,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.Transitions; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -113,7 +114,7 @@ public class LegacySplitScreenController implements LegacySplitScreen, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, Handler handler, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - TaskStackListenerImpl taskStackListener) { + TaskStackListenerImpl taskStackListener, Transitions transitions) { mContext = context; mDisplayController = displayController; mSystemWindows = systemWindows; @@ -123,7 +124,8 @@ public class LegacySplitScreenController implements LegacySplitScreen, mTransactionPool = transactionPool; mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); mTaskOrganizer = shellTaskOrganizer; - mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, syncQueue); + mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions, + syncQueue); mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler, shellTaskOrganizer); mRotationController = @@ -553,8 +555,36 @@ public class LegacySplitScreenController implements LegacySplitScreen, mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout); } + void prepareEnterSplitTransition(WindowContainerTransaction outWct) { + // Set resizable directly here because buildEnterSplit already resizes home stack. + mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout); + } + + void finishEnterSplitTransition(boolean minimized) { + update(mDisplayController.getDisplayContext( + mContext.getDisplayId()).getResources().getConfiguration()); + if (minimized) { + ensureMinimizedSplit(); + } else { + ensureNormalSplit(); + } + } + void startDismissSplit(boolean toPrimaryTask) { + startDismissSplit(toPrimaryTask, false /* snapped */); + } + + void startDismissSplit(boolean toPrimaryTask, boolean snapped) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mSplits.getSplitTransitions().dismissSplit( + mSplits, mSplitLayout, !toPrimaryTask, snapped); + } else { mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask); + onDismissSplit(); + } + } + + void onDismissSplit() { updateVisibility(false /* visible */); mMinimized = false; // Resets divider bar position to undefined, so new divider bar will apply default position diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java index 02c82dee8ca46..8c624cdb10639 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java @@ -32,6 +32,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.TaskOrganizer; import androidx.annotation.NonNull; @@ -63,11 +64,17 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { final SurfaceSession mSurfaceSession = new SurfaceSession(); + private final SplitScreenTransitions mSplitTransitions; + LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController, ShellTaskOrganizer shellTaskOrganizer, + Transitions transitions, SyncTransactionQueue syncQueue) { mSplitScreenController = splitScreenController; mTaskOrganizer = shellTaskOrganizer; + mSplitTransitions = new SplitScreenTransitions(splitScreenController.mTransactionPool, + transitions, mSplitScreenController, this); + transitions.addHandler(mSplitTransitions); mSyncQueue = syncQueue; } @@ -98,6 +105,14 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { mSplitScreenController.mTransactionPool.release(t); } + TaskOrganizer getTaskOrganizer() { + return mTaskOrganizer; + } + + SplitScreenTransitions getSplitTransitions() { + return mSplitTransitions; + } + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (this) { @@ -195,10 +210,12 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { mLeashByTaskId.put(taskInfo.taskId, leash); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); } private void handleChildTaskChanged(RunningTaskInfo taskInfo) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); } @@ -241,14 +258,15 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { } else if (info.token.asBinder() == mSecondary.token.asBinder()) { mSecondary = info; } + if (DEBUG) { + Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); + } + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS && mSplitScreenController.isHomeStackResizable()); - if (DEBUG) { - Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); - } if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty && secondaryImpliedMinimize == secondaryImpliesMinimize) { // No relevant changes diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java new file mode 100644 index 0000000000000..93520c0b59dee --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java @@ -0,0 +1,342 @@ +/* + * 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.legacysplitscreen; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.Transitions; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ExternalThread; + +import java.util.ArrayList; + +/** Plays transition animations for split-screen */ +public class SplitScreenTransitions implements Transitions.TransitionHandler { + private static final String TAG = "SplitScreenTransitions"; + + public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10; + + private final TransactionPool mTransactionPool; + private final Transitions mTransitions; + private final LegacySplitScreenController mSplitScreen; + private final LegacySplitScreenTaskListener mListener; + + private IBinder mPendingDismiss = null; + private boolean mDismissFromSnap = false; + private IBinder mPendingEnter = null; + private IBinder mAnimatingTransition = null; + + /** Keeps track of currently running animations */ + private final ArrayList mAnimations = new ArrayList<>(); + + private Runnable mFinishCallback = null; + private SurfaceControl.Transaction mFinishTransaction; + + SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, + @NonNull LegacySplitScreenController splitScreen, + @NonNull LegacySplitScreenTaskListener listener) { + mTransactionPool = pool; + mTransitions = transitions; + mSplitScreen = splitScreen; + mListener = listener; + } + + @Override + public WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type, + @NonNull IBinder transition, @Nullable ActivityManager.RunningTaskInfo triggerTask) { + WindowContainerTransaction out = null; + if (mSplitScreen.isDividerVisible()) { + // try to handle everything while in split-screen + out = new WindowContainerTransaction(); + if (triggerTask != null) { + final boolean shouldDismiss = + // if we close the primary-docked task, then leave split-screen since there + // is nothing behind it. + ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) + && triggerTask.parentTaskId == mListener.mPrimary.taskId) + // if a non-resizable is launched, we also need to leave split-screen. + || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) + && !triggerTask.isResizeable); + // In both cases, dismiss the primary + if (shouldDismiss) { + WindowManagerProxy.buildDismissSplit(out, mListener, + mSplitScreen.getSplitLayout(), true /* dismiss */); + if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) { + out.reorder(triggerTask.token, true /* onTop */); + } + mPendingDismiss = transition; + } + } + } else if (triggerTask != null) { + // Not in split mode, so look for an open with a trigger task. + if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) + && triggerTask.configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + out = new WindowContainerTransaction(); + mSplitScreen.prepareEnterSplitTransition(out); + mPendingEnter = transition; + } + } + return out; + } + + // TODO(shell-transitions): real animations + private void startExampleAnimation(@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); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + 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) { } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } + + // TODO(shell-transitions): real animations + private void startExampleResizeAnimation(@NonNull SurfaceControl leash, + @NonNull Rect startBounds, @NonNull Rect endBounds) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setWindowCrop(leash, + (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), + (int) (startBounds.height() * (1.f - fraction) + + endBounds.height() * fraction)); + transaction.setPosition(leash, + startBounds.left * (1.f - fraction) + endBounds.left * fraction, + startBounds.top * (1.f - fraction) + endBounds.top * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setWindowCrop(leash, 0, 0); + transaction.setPosition(leash, endBounds.left, endBounds.top); + transaction.apply(); + mTransactionPool.release(transaction); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + if (transition != mPendingDismiss && transition != mPendingEnter) { + // If we're not in split-mode, just abort + if (!mSplitScreen.isDividerVisible()) return false; + // Check to see if HOME is involved + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null + || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue; + if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + mSplitScreen.ensureMinimizedSplit(); + } else if (change.getMode() == TRANSIT_CLOSE + || change.getMode() == TRANSIT_TO_BACK) { + mSplitScreen.ensureNormalSplit(); + } + } + // Use normal animations. + return false; + } + + mFinishCallback = finishCallback; + mFinishTransaction = mTransactionPool.acquire(); + mAnimatingTransition = transition; + + // Play fade animations + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); + final int mode = info.getChanges().get(i).getMode(); + + if (mode == TRANSIT_CHANGE) { + if (change.getParent() != null) { + // This is probably reparented, so we want the parent to be immediately visible + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + t.show(parentChange.getLeash()); + t.setAlpha(parentChange.getLeash(), 1.f); + // and then animate this layer outside the parent (since, for example, this is + // the home task animating from fullscreen to part-screen). + t.reparent(leash, info.getRootLeash()); + t.setLayer(leash, info.getChanges().size() - i); + // build the finish reparent/reposition + mFinishTransaction.reparent(leash, parentChange.getLeash()); + mFinishTransaction.setPosition(leash, + change.getEndRelOffset().x, change.getEndRelOffset().y); + } + // TODO(shell-transitions): screenshot here + final Rect startBounds = new Rect(change.getStartAbsBounds()); + final boolean isHome = change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; + if (mPendingDismiss == transition && mDismissFromSnap && !isHome) { + // Home is special since it doesn't move during fling. Everything else, though, + // when dismissing from snap, the top/left is at 0,0. + startBounds.offsetTo(0, 0); + } + final Rect endBounds = new Rect(change.getEndAbsBounds()); + startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + startExampleResizeAnimation(leash, startBounds, endBounds); + } + if (change.getParent() != null) { + continue; + } + + if (transition == mPendingEnter + && mListener.mPrimary.token.equals(change.getContainer()) + || mListener.mSecondary.token.equals(change.getContainer())) { + t.setWindowCrop(leash, change.getStartAbsBounds().width(), + change.getStartAbsBounds().height()); + if (mListener.mPrimary.token.equals(change.getContainer())) { + // Move layer to top since we want it above the oversized home task during + // animation even though home task is on top in hierarchy. + t.setLayer(leash, info.getChanges().size() + 1); + } + } + boolean isOpening = Transitions.isOpeningType(info.getType()); + if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { + // fade in + startExampleAnimation(leash, true /* show */); + } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { + // fade out + if (transition == mPendingDismiss && mDismissFromSnap) { + // Dismissing via snap-to-top/bottom means that the dismissed task is already + // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 + // and don't animate it so it doesn't pop-in when reparented. + t.setAlpha(leash, 0.f); + } else { + startExampleAnimation(leash, false /* show */); + } + } + } + if (transition == mPendingEnter) { + // If entering, check if we should enter into minimized or normal split + boolean homeIsVisible = false; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null + || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) { + continue; + } + homeIsVisible = change.getMode() == TRANSIT_OPEN + || change.getMode() == TRANSIT_TO_FRONT + || change.getMode() == TRANSIT_CHANGE; + break; + } + mSplitScreen.finishEnterSplitTransition(homeIsVisible); + } + t.apply(); + onFinish(); + return true; + } + + @ExternalThread + void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, + boolean dismissOrMaximize, boolean snapped) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize); + mTransitions.getMainExecutor().execute(() -> { + mDismissFromSnap = snapped; + mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this); + }); + } + + private void onFinish() { + if (!mAnimations.isEmpty()) return; + mFinishTransaction.apply(); + mTransactionPool.release(mFinishTransaction); + mFinishTransaction = null; + mFinishCallback.run(); + mFinishCallback = null; + if (mAnimatingTransition == mPendingEnter) { + mPendingEnter = null; + } + if (mAnimatingTransition == mPendingDismiss) { + mSplitScreen.onDismissSplit(); + mPendingDismiss = null; + } + mDismissFromSnap = false; + mAnimatingTransition = null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index 68da35d9c4b1e..90a8de02deb8e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -39,6 +39,7 @@ import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; +import com.android.wm.shell.Transitions; import com.android.wm.shell.common.SyncTransactionQueue; import java.util.ArrayList; @@ -90,7 +91,11 @@ class WindowManagerProxy { void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) { - mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize)); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */); + } else { + mExecutor.execute(() -> applyDismissSplit(tiles, layout, dismissOrMaximize)); + } } public void setResizing(final boolean resizing) { @@ -181,6 +186,18 @@ class WindowManagerProxy { return isHomeResizable; } + /** @see #buildEnterSplit */ + boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) { + // Set launchtile first so that any stack created after + // getAllRootTaskInfos and before reparent (even if unlikely) are placed + // correctly. + mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token); + WindowContainerTransaction wct = new WindowContainerTransaction(); + final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout); + applySyncTransaction(wct); + return isHomeResizable; + } + /** * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split. * This assumes there is already something in the primary split since that is usually what @@ -189,14 +206,10 @@ class WindowManagerProxy { * * @return whether the home stack is resizable */ - boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) { - // Set launchtile first so that any stack created after - // getAllRootTaskInfos and before reparent (even if unlikely) are placed - // correctly. - mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token); + boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles, + LegacySplitDisplayLayout layout) { List rootTasks = mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */); - WindowContainerTransaction wct = new WindowContainerTransaction(); if (rootTasks.isEmpty()) { return false; } @@ -215,48 +228,60 @@ class WindowManagerProxy { // Since this iterates from bottom to top, update topHomeTask for every fullscreen task // so it will be left with the status of the top one. topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null; - wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); + outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); } // Move the secondary split-forward. - wct.reorder(tiles.mSecondary.token, true /* onTop */); - boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); - if (topHomeTask != null) { + outWct.reorder(tiles.mSecondary.token, true /* onTop */); + boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, + outWct); + if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) { // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST // is enabled, this temporarily syncs the home surface position with offset until // sync transaction finishes. - wct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds); + outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds); } - applySyncTransaction(wct); return isHomeResizable; } - boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { + static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { final int atype = ti.getActivityType(); return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS; } + /** @see #buildDismissSplit */ + void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, + boolean dismissOrMaximize) { + // Set launch root first so that any task created after getChildContainers and + // before reparent (pretty unlikely) are put into fullscreen. + mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null); + // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished + // plus specific APIs to clean this up. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + buildDismissSplit(wct, tiles, layout, dismissOrMaximize); + applySyncTransaction(wct); + } + /** * Reparents all tile members back to their display and resets home task override bounds. * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary * split (thus resulting in the top of the secondary split becoming * fullscreen. {@code false} resolves the other way. */ - void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, + static void buildDismissSplit(WindowContainerTransaction outWct, + LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, boolean dismissOrMaximize) { - // Set launch root first so that any task created after getChildContainers and - // before reparent (pretty unlikely) are put into fullscreen. - mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null); // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished // plus specific APIs to clean this up. + final TaskOrganizer taskOrg = tiles.getTaskOrganizer(); List primaryChildren = - mTaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); + taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); List secondaryChildren = - mTaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); + taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); // In some cases (eg. non-resizable is launched), system-server will leave split-screen. // as a result, the above will not capture any tasks; yet, we need to clean-up the // home task bounds. List freeHomeAndRecents = - mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); + taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); // Filter out the root split tasks freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token) || p.token.equals(tiles.mPrimary.token)); @@ -265,11 +290,10 @@ class WindowManagerProxy { && freeHomeAndRecents.isEmpty()) { return; } - WindowContainerTransaction wct = new WindowContainerTransaction(); if (dismissOrMaximize) { // Dismissing, so move all primary split tasks first for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, + outWct.reparent(primaryChildren.get(i).token, null /* parent */, true /* onTop */); } boolean homeOnTop = false; @@ -277,16 +301,16 @@ class WindowManagerProxy { // order within the secondary split. for (int i = secondaryChildren.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - wct.reparent(ti.token, null /* parent */, true /* onTop */); + outWct.reparent(ti.token, null /* parent */, true /* onTop */); if (isHomeOrRecentTask(ti)) { - wct.setBounds(ti.token, null); - wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(ti.token, null); + outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); if (i == 0) { homeOnTop = true; } } } - if (homeOnTop) { + if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) { // Translate/update-crop of secondary out-of-band with sync transaction -- instead // play this in sync with new home-app frame because until BALST is enabled this // shows up on screen before the syncTransaction returns. @@ -304,7 +328,7 @@ class WindowManagerProxy { layout.mDisplayLayout.height()); crop.offset(-posX, -posY); sft.setWindowCrop(tiles.mSecondarySurface, crop); - wct.setBoundsChangeTransaction(tiles.mSecondary.token, sft); + outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft); } } else { // Maximize, so move non-home secondary split first @@ -312,7 +336,7 @@ class WindowManagerProxy { if (isHomeOrRecentTask(secondaryChildren.get(i))) { continue; } - wct.reparent(secondaryChildren.get(i).token, null /* parent */, + outWct.reparent(secondaryChildren.get(i).token, null /* parent */, true /* onTop */); } // Find and place home tasks in-between. This simulates the fact that there was @@ -320,24 +344,23 @@ class WindowManagerProxy { for (int i = secondaryChildren.size() - 1; i >= 0; --i) { final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); if (isHomeOrRecentTask(ti)) { - wct.reparent(ti.token, null /* parent */, true /* onTop */); + outWct.reparent(ti.token, null /* parent */, true /* onTop */); // reset bounds and mode too - wct.setBounds(ti.token, null); - wct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(ti.token, null); + outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); } } for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, + outWct.reparent(primaryChildren.get(i).token, null /* parent */, true /* onTop */); } } for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { - wct.setBounds(freeHomeAndRecents.get(i).token, null); - wct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED); + outWct.setBounds(freeHomeAndRecents.get(i).token, null); + outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED); } // Reset focusable to true - wct.setFocusable(tiles.mPrimary.token, true /* focusable */); - applySyncTransaction(wct); + outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 926062b1623fe..f9505dec78fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -28,9 +28,9 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.util.Optional; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index a1bde589d5c14..943a54e61c03f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -22,9 +22,9 @@ import com.android.wm.shell.ShellInit; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.util.Optional; diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 6d9d587bc729a..54e30af675ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -91,11 +91,11 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEvents; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java index 765fd32f7cd23..1049548c382c4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -23,6 +23,7 @@ import android.view.IWindowManager; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.Transitions; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.SyncTransactionQueue; @@ -58,9 +59,10 @@ public class TvWMShellModule { DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { + SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener, + Transitions transitions) { return new LegacySplitScreenController(context, displayController, systemWindows, displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, - taskStackListener); + taskStackListener, transitions); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 3399a0bfc72e6..9ec7657c5dd51 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -23,6 +23,7 @@ import android.view.IWindowManager; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.Transitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairsController; @@ -35,6 +36,8 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -46,8 +49,6 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipTouchHandler; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import java.util.Optional; import java.util.concurrent.Executor; @@ -76,10 +77,11 @@ public class WMShellModule { DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { + SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener, + Transitions transitions) { return new LegacySplitScreenController(context, displayController, systemWindows, displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, - taskStackListener); + taskStackListener, transitions); } @WMSingleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java index b3176ddeaf656..4d32a3b4077f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java @@ -40,8 +40,8 @@ import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.wm.shell.pip.Pip; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; +import com.android.wm.shell.pip.Pip; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 73d87b0d45963..31bf7120900fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -35,12 +35,12 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedGestureHandler; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.phone.PipTouchHandler; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import org.junit.Before; import org.junit.Test; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5289f8604f361..375c3e138a398 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1618,7 +1618,8 @@ class ActivityStarter { mService.getTransitionController().collectExistenceChange(r); } if (newTransition != null) { - mService.getTransitionController().requestStartTransition(newTransition); + mService.getTransitionController().requestStartTransition(newTransition, + r.getTask()); } else { // Make the collecting transition wait until this request is ready. mService.getTransitionController().setReady(false); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 9d291b12c2c93..73a6efdb67997 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2207,6 +2207,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) { + if (mService.getTransitionController().getTransitionPlayer() != null) return; // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. final TaskDisplayArea taskDisplayArea = task.getDisplayArea(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index be78d7a18377c..b37e3c42f568a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -37,6 +37,7 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -567,6 +568,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe tmpList.add(wc); } for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { + if (!p.isAttached() || !changes.get(p).hasChanged(p)) { + // Again, we're skipping no-ops + break; + } if (participants.contains(p)) { topParent = p; break; @@ -695,6 +700,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final TransitionInfo.Change change = new TransitionInfo.Change( target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() : null, target.getSurfaceControl()); + // TODO(shell-transitions): Use leash for non-organized windows. if (info.mParent != null) { change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); } @@ -704,6 +710,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left, target.getBounds().top - target.getParent().getBounds().top); change.setFlags(info.getChangeFlags(target)); + final Task task = target.asTask(); + if (task != null) { + final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); + task.fillTaskInfo(tinfo); + change.setTaskInfo(tinfo); + } out.addChange(change); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 2f5d10afe3dae..0fe0afa54dd38 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -167,7 +168,8 @@ class TransitionController { // Make the collecting transition wait until this request is ready. mCollectingTransition.setReady(false); } else { - newTransition = requestStartTransition(createTransition(type, flags)); + newTransition = requestStartTransition(createTransition(type, flags), + trigger != null ? trigger.asTask() : null); } if (trigger != null) { if (isExistenceType(type)) { @@ -181,11 +183,16 @@ class TransitionController { /** Asks the transition player (shell) to start a created but not yet started transition. */ @NonNull - Transition requestStartTransition(@NonNull Transition transition) { + Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask) { try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); - mTransitionPlayer.requestStartTransition(transition.mType, transition); + ActivityManager.RunningTaskInfo info = null; + if (startTask != null) { + info = new ActivityManager.RunningTaskInfo(); + startTask.fillTaskInfo(info); + } + mTransitionPlayer.requestStartTransition(transition.mType, transition, info); } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); transition.start(); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5f2b628e056a0..b0e67ce177118 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -19,8 +19,8 @@ package com.android.server.wm; import static android.Manifest.permission.READ_FRAME_BUFFER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; -import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; +import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; 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; @@ -163,6 +163,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub try { synchronized (mGlobalLock) { Transition transition = Transition.fromBinder(transitionToken); + // In cases where transition is already provided, the "readiness lifecycle" of the + // transition is determined outside of this transaction. However, if this is a + // direct call from shell, the entire transition lifecycle is contained in the + // provided transaction and thus we can setReady immediately after apply. + boolean needsSetReady = transition == null && t != null; if (transition == null) { if (type < 0) { throw new IllegalArgumentException("Can't create transition with no type"); @@ -174,6 +179,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub t = new WindowContainerTransaction(); } applyTransaction(t, -1 /*syncId*/, transition); + if (needsSetReady) { + transition.setReady(); + } return transition; } } finally { @@ -258,14 +266,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (transition != null) { transition.collect(wc); - if (hop.isReparent() && hop.getNewParent() != null) { - final WindowContainer parentWc = - WindowContainer.fromBinder(hop.getNewParent()); - if (parentWc == null) { - Slog.e(TAG, "Can't resolve parent window from token"); - continue; + if (hop.isReparent()) { + if (wc.getParent() != null) { + // Collect the current parent. It's visibility may change as a result + // of this reparenting. + transition.collect(wc.getParent()); + } + if (hop.getNewParent() != null) { + final WindowContainer parentWc = + WindowContainer.fromBinder(hop.getNewParent()); + if (parentWc == null) { + Slog.e(TAG, "Can't resolve parent window from token"); + continue; + } + transition.collect(parentWc); } - transition.collect(parentWc); } } effects |= sanitizeAndApplyHierarchyOp(wc, hop); @@ -307,6 +322,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { // Already calls ensureActivityConfig mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { final PooledConsumer f = PooledLambda.obtainConsumer( ActivityRecord::ensureActivityConfiguration, diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 74248a950a383..401ace03c5546 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -77,6 +77,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + fillChangeMap(changes, newTask); // End states. closing.mVisibleRequested = false; opening.mVisibleRequested = true; @@ -141,6 +142,7 @@ public class TransitionTests extends WindowTestsBase { changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + fillChangeMap(changes, newTask); // End states. closing.mVisibleRequested = false; opening.mVisibleRequested = true; @@ -189,6 +191,8 @@ public class TransitionTests extends WindowTestsBase { changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + fillChangeMap(changes, tda); + // End states. showing.mVisibleRequested = true; showing2.mVisibleRequested = true; @@ -338,4 +342,12 @@ public class TransitionTests extends WindowTestsBase { assertEquals(FLAG_SHOW_WALLPAPER, info.getChange( tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags()); } + + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ + private static void fillChangeMap(ArrayMap changes, + WindowContainer top) { + for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) { + changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + } + } }