diff options
| author | 2021-12-10 17:34:11 +0000 | |
|---|---|---|
| committer | 2021-12-10 17:34:11 +0000 | |
| commit | 0c379593e58b6c0911e6d0deadb260034a0935fb (patch) | |
| tree | f55415a38d61f63eb2c5749462c2a0c38465139f | |
| parent | c0fed62448b26719c41518f905d2781055d979bc (diff) | |
| parent | 927f9f5c11e95f79b949a68ee85cdd42ced696a2 (diff) | |
Merge "Hook-up TaskView/bubbles to shell transition"
11 files changed, 619 insertions, 28 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index 2f3214d1d1ab..54e743f72cc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -41,6 +41,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -77,6 +78,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final ShellTaskOrganizer mTaskOrganizer; private final Executor mShellExecutor; private final SyncTransactionQueue mSyncQueue; + private final TaskViewTransitions mTaskViewTransitions; private ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; @@ -92,17 +94,27 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final Rect mTmpRootRect = new Rect(); private final int[] mTmpLocation = new int[2]; - public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) { + public TaskView(Context context, ShellTaskOrganizer organizer, + TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { super(context, null, 0, 0, true /* disableBackgroundLayer */); mTaskOrganizer = organizer; mShellExecutor = organizer.getExecutor(); mSyncQueue = syncQueue; + mTaskViewTransitions = taskViewTransitions; + if (mTaskViewTransitions != null) { + mTaskViewTransitions.addTaskView(this); + } setUseAlpha(); getHolder().addCallback(this); mGuard.open("release"); } + /** Until all users are converted, we may have mixed-use (eg. Car). */ + private boolean isUsingShellTransitions() { + return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS; + } + /** * Only one listener may be set on the view, throws an exception otherwise. */ @@ -129,6 +141,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @NonNull ActivityOptions options, @Nullable Rect launchBounds) { prepareActivityOptions(options, launchBounds); LauncherApps service = mContext.getSystemService(LauncherApps.class); + if (isUsingShellTransitions()) { + mShellExecutor.execute(() -> { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle()); + mTaskViewTransitions.startTaskView(wct, this); + }); + return; + } try { service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle()); } catch (Exception e) { @@ -148,6 +168,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds) { prepareActivityOptions(options, launchBounds); + if (isUsingShellTransitions()) { + mShellExecutor.execute(() -> { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle()); + mTaskViewTransitions.startTaskView(wct, this); + }); + return; + } try { pendingIntent.send(mContext, 0 /* code */, fillInIntent, null /* onFinished */, null /* handler */, null /* requiredPermission */, @@ -177,6 +205,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, mObscuredTouchRect = obscuredRect; } + private void onLocationChanged(WindowContainerTransaction wct) { + // Update based on the screen bounds + getBoundsOnScreen(mTmpRect); + getRootView().getBoundsOnScreen(mTmpRootRect); + if (!mTmpRootRect.contains(mTmpRect)) { + mTmpRect.offsetTo(0, 0); + } + wct.setBounds(mTaskToken, mTmpRect); + } + /** * Call when view position or size has changed. Do not call when animating. */ @@ -184,15 +222,12 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, if (mTaskToken == null) { return; } - // Update based on the screen bounds - getBoundsOnScreen(mTmpRect); - getRootView().getBoundsOnScreen(mTmpRootRect); - if (!mTmpRootRect.contains(mTmpRect)) { - mTmpRect.offsetTo(0, 0); - } + // Sync Transactions can't operate simultaneously with shell transition collection. + // The transition animation (upon showing) will sync the location itself. + if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return; WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setBounds(mTaskToken, mTmpRect); + onLocationChanged(wct); mSyncQueue.queue(wct); } @@ -217,6 +252,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private void performRelease() { getHolder().removeCallback(this); + if (mTaskViewTransitions != null) { + mTaskViewTransitions.removeTaskView(this); + } mShellExecutor.execute(() -> { mTaskOrganizer.removeListener(this); resetTaskInfo(); @@ -254,6 +292,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + if (isUsingShellTransitions()) { + // Everything else handled by enter transition. + return; + } mTaskInfo = taskInfo; mTaskToken = taskInfo.token; mTaskLeash = leash; @@ -288,6 +330,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that + // we know about -- so leave clean-up here even if shell transitions are enabled. if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; if (mListener != null) { @@ -355,6 +399,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, // Nothing to update, task is not yet available return; } + if (isUsingShellTransitions()) { + mTaskViewTransitions.setTaskViewVisible(this, true /* visible */); + return; + } // Reparent the task when this surface is created mTransaction.reparent(mTaskLeash, getSurfaceControl()) .show(mTaskLeash) @@ -380,6 +428,11 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, return; } + if (isUsingShellTransitions()) { + mTaskViewTransitions.setTaskViewVisible(this, false /* visible */); + return; + } + // Unparent the task when this surface is destroyed mTransaction.reparent(mTaskLeash, null).apply(); updateTaskVisibility(); @@ -421,4 +474,91 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, super.onDetachedFromWindow(); getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } + + ActivityManager.RunningTaskInfo getTaskInfo() { + return mTaskInfo; + } + + void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) { + if (mTaskToken == null) { + // Nothing to update, task is not yet available + return; + } + + finishTransaction.reparent(mTaskLeash, null).apply(); + + if (mListener != null) { + final int taskId = mTaskInfo.taskId; + mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); + } + } + + /** + * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide + * is used instead. + */ + void prepareCloseAnimation() { + if (mTaskToken != null) { + if (mListener != null) { + final int taskId = mTaskInfo.taskId; + mListenerExecutor.execute(() -> { + mListener.onTaskRemovalStarted(taskId); + }); + } + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); + } + resetTaskInfo(); + } + + void prepareOpenAnimation(final boolean newTask, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, + WindowContainerTransaction wct) { + mTaskInfo = taskInfo; + mTaskToken = mTaskInfo.token; + mTaskLeash = leash; + if (mSurfaceCreated) { + // Surface is ready, so just reparent the task to this surface control + startTransaction.reparent(mTaskLeash, getSurfaceControl()) + .show(mTaskLeash) + .apply(); + // Also reparent on finishTransaction since the finishTransaction will reparent back + // to its "original" parent by default. + finishTransaction.reparent(mTaskLeash, getSurfaceControl()) + .setPosition(mTaskLeash, 0, 0) + .apply(); + + // TODO: determine if this is really necessary or not + onLocationChanged(wct); + } else { + // The surface has already been destroyed before the task has appeared, + // so go ahead and hide the task entirely + wct.setHidden(mTaskToken, true /* hidden */); + // listener callback is below + } + if (newTask) { + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */); + } + + if (mTaskInfo.taskDescription != null) { + int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor(); + setResizeBackgroundColor(startTransaction, backgroundColor); + } + + if (mListener != null) { + final int taskId = mTaskInfo.taskId; + final ComponentName baseActivity = mTaskInfo.baseActivity; + + mListenerExecutor.execute(() -> { + if (newTask) { + mListener.onTaskCreated(taskId, baseActivity); + } + // Even if newTask, send a visibilityChange if the surface was destroyed. + if (!newTask || !mSurfaceCreated) { + mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); + } + }); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java index 8286d102791e..42844b57b92a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java @@ -31,13 +31,24 @@ public class TaskViewFactoryController { private final ShellTaskOrganizer mTaskOrganizer; private final ShellExecutor mShellExecutor; private final SyncTransactionQueue mSyncQueue; + private final TaskViewTransitions mTaskViewTransitions; private final TaskViewFactory mImpl = new TaskViewFactoryImpl(); public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer, + ShellExecutor shellExecutor, SyncTransactionQueue syncQueue, + TaskViewTransitions taskViewTransitions) { + mTaskOrganizer = taskOrganizer; + mShellExecutor = shellExecutor; + mSyncQueue = syncQueue; + mTaskViewTransitions = taskViewTransitions; + } + + public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer, ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) { mTaskOrganizer = taskOrganizer; mShellExecutor = shellExecutor; mSyncQueue = syncQueue; + mTaskViewTransitions = null; } public TaskViewFactory asTaskViewFactory() { @@ -46,7 +57,7 @@ public class TaskViewFactoryController { /** Creates an {@link TaskView} */ public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) { - TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue); + TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); executor.execute(() -> { onCreate.accept(taskView); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java new file mode 100644 index 000000000000..83335ac24799 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.transition.Transitions; + +import java.util.ArrayList; + +/** + * Handles Shell Transitions that involve TaskView tasks. + */ +public class TaskViewTransitions implements Transitions.TransitionHandler { + private static final String TAG = "TaskViewTransitions"; + + private final ArrayList<TaskView> mTaskViews = new ArrayList<>(); + private final ArrayList<PendingTransition> mPending = new ArrayList<>(); + private final Transitions mTransitions; + private final boolean[] mRegistered = new boolean[]{ false }; + + /** + * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be + * in-flight (collecting) at a time (because otherwise, the operations could get merged into + * a single transition). So, keep a queue here until we add a queue in server-side. + */ + private static class PendingTransition { + final @WindowManager.TransitionType int mType; + final WindowContainerTransaction mWct; + final @NonNull TaskView mTaskView; + IBinder mClaimed; + + PendingTransition(@WindowManager.TransitionType int type, + @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) { + mType = type; + mWct = wct; + mTaskView = taskView; + } + } + + public TaskViewTransitions(Transitions transitions) { + mTransitions = transitions; + // Defer registration until the first TaskView because we want this to be the "first" in + // priority when handling requests. + // TODO(210041388): register here once we have an explicit ordering mechanism. + } + + void addTaskView(TaskView tv) { + synchronized (mRegistered) { + if (!mRegistered[0]) { + mRegistered[0] = true; + mTransitions.addHandler(this); + } + } + mTaskViews.add(tv); + } + + void removeTaskView(TaskView tv) { + mTaskViews.remove(tv); + // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell + } + + /** + * Looks through the pending transitions for one matching `taskView`. + * @param taskView the pending transition should be for this. + * @param closing When true, this only returns a pending transition of the close/hide type. + * Otherwise it selects open/show. + * @param latest When true, this will only check the most-recent pending transition for the + * specified taskView. If it doesn't match `closing`, this will return null even + * if there is a match earlier. The idea behind this is to check the state of + * the taskviews "as if all transitions already happened". + */ + private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) { + for (int i = mPending.size() - 1; i >= 0; --i) { + if (mPending.get(i).mTaskView != taskView) continue; + if (Transitions.isClosingType(mPending.get(i).mType) == closing) { + return mPending.get(i); + } + if (latest) { + return null; + } + } + return null; + } + + private PendingTransition findPending(IBinder claimed) { + for (int i = 0; i < mPending.size(); ++i) { + if (mPending.get(i).mClaimed != claimed) continue; + return mPending.get(i); + } + return null; + } + + /** @return whether there are pending transitions on TaskViews. */ + public boolean hasPending() { + return !mPending.isEmpty(); + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + if (triggerTask == null) { + return null; + } + final TaskView taskView = findTaskView(triggerTask); + if (taskView == null) return null; + // Opening types should all be initiated by shell + if (!Transitions.isClosingType(request.getType())) return null; + PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */); + if (pending == null) { + pending = new PendingTransition(request.getType(), null, taskView); + } + if (pending.mClaimed != null) { + throw new IllegalStateException("Task is closing in 2 collecting transitions?" + + " This state doesn't make sense"); + } + pending.mClaimed = transition; + return new WindowContainerTransaction(); + } + + private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) { + for (int i = 0; i < mTaskViews.size(); ++i) { + if (mTaskViews.get(i).getTaskInfo() == null) continue; + if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) { + return mTaskViews.get(i); + } + } + return null; + } + + void startTaskView(WindowContainerTransaction wct, TaskView taskView) { + mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView)); + startNextTransition(); + } + + void setTaskViewVisible(TaskView taskView, boolean visible) { + PendingTransition pending = findPending(taskView, !visible, true /* latest */); + if (pending != null) { + // Already opening or creating a task, so no need to do anything here. + return; + } + if (taskView.getTaskInfo() == null) { + // Nothing to update, task is not yet available + return; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */); + pending = new PendingTransition( + visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView); + mPending.add(pending); + startNextTransition(); + // visibility is reported in transition. + } + + private void startNextTransition() { + if (mPending.isEmpty()) return; + final PendingTransition pending = mPending.get(0); + if (pending.mClaimed != null) { + // Wait for this to start animating. + return; + } + pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + PendingTransition pending = findPending(transition); + if (pending == null) return false; + mPending.remove(pending); + TaskView taskView = pending.mTaskView; + final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>(); + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change chg = info.getChanges().get(i); + if (chg.getTaskInfo() == null) continue; + tasks.add(chg); + } + if (tasks.isEmpty()) { + Slog.e(TAG, "Got a TaskView transition with no task."); + return false; + } + WindowContainerTransaction wct = null; + for (int i = 0; i < tasks.size(); ++i) { + TransitionInfo.Change chg = tasks.get(i); + if (Transitions.isClosingType(chg.getMode())) { + final boolean isHide = chg.getMode() == TRANSIT_TO_BACK; + TaskView tv = findTaskView(chg.getTaskInfo()); + if (tv == null) { + throw new IllegalStateException("TaskView transition is closing a " + + "non-taskview task "); + } + if (isHide) { + tv.prepareHideAnimation(finishTransaction); + } else { + tv.prepareCloseAnimation(); + } + } else if (Transitions.isOpeningType(chg.getMode())) { + final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN; + if (wct == null) wct = new WindowContainerTransaction(); + TaskView tv = taskView; + if (!taskIsNew) { + tv = findTaskView(chg.getTaskInfo()); + if (tv == null) { + throw new IllegalStateException("TaskView transition is showing a " + + "non-taskview task "); + } + } + tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction, + chg.getTaskInfo(), chg.getLeash(), wct); + } else { + throw new IllegalStateException("Claimed transition isn't an opening or closing" + + " type: " + chg.getMode()); + } + } + // No animation, just show it immediately. + startTransaction.apply(); + finishTransaction.apply(); + finishCallback.onTransitionFinished(wct, null /* wctCB */); + startNextTransition(); + return true; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index ec59fad3e95b..f979f4d3b41f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -80,6 +80,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; @@ -136,6 +137,7 @@ public class BubbleController { private final TaskStackListenerImpl mTaskStackListener; private final ShellTaskOrganizer mTaskOrganizer; private final DisplayController mDisplayController; + private final TaskViewTransitions mTaskViewTransitions; private final SyncTransactionQueue mSyncQueue; // Used to post to main UI thread @@ -212,6 +214,7 @@ public class BubbleController { DisplayController displayController, ShellExecutor mainExecutor, Handler mainHandler, + TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { BubbleLogger logger = new BubbleLogger(uiEventLogger); BubblePositioner positioner = new BubblePositioner(context, windowManager); @@ -220,7 +223,7 @@ public class BubbleController { new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger, taskStackListener, organizer, positioner, displayController, mainExecutor, - mainHandler, syncQueue); + mainHandler, taskViewTransitions, syncQueue); } /** @@ -243,6 +246,7 @@ public class BubbleController { DisplayController displayController, ShellExecutor mainExecutor, Handler mainHandler, + TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; mLauncherApps = launcherApps; @@ -266,6 +270,7 @@ public class BubbleController { mSavedBubbleKeysPerUser = new SparseSetArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); mDisplayController = displayController; + mTaskViewTransitions = taskViewTransitions; mSyncQueue = syncQueue; } @@ -570,6 +575,10 @@ public class BubbleController { return mSyncQueue; } + TaskViewTransitions getTaskViewTransitions() { + return mTaskViewTransitions; + } + /** Contains information to help position things on the screen. */ BubblePositioner getPositioner() { return mBubblePositioner; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index a87aad4261a6..af59062ba0f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -335,7 +335,7 @@ public class BubbleExpandedView extends LinearLayout { mManageButton.setVisibility(GONE); } else { mTaskView = new TaskView(mContext, mController.getTaskOrganizer(), - mController.getSyncTransactionQueue()); + mController.getTaskViewTransitions(), mController.getSyncTransactionQueue()); mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener); mExpandedViewContainer.addView(mTaskView); bringChildToFront(mTaskView); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 54ce6bb7acba..7bc8d23d81de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -36,6 +36,7 @@ import com.android.wm.shell.ShellInitImpl; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.TaskViewFactoryController; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairsController; @@ -463,6 +464,12 @@ public abstract class WMShellBaseModule { animExecutor); } + @WMSingleton + @Provides + static TaskViewTransitions provideTaskViewTransitions(Transitions transitions) { + return new TaskViewTransitions(transitions); + } + // // Display areas // @@ -594,8 +601,10 @@ public abstract class WMShellBaseModule { static TaskViewFactoryController provideTaskViewFactoryController( ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor, - SyncTransactionQueue syncQueue) { - return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue); + SyncTransactionQueue syncQueue, + TaskViewTransitions taskViewTransitions) { + return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue, + taskViewTransitions); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index f562fd9cf1af..dff563589e5b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -27,6 +27,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.bubbles.BubbleController; @@ -108,11 +109,13 @@ public class WMShellModule { DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, + TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { return BubbleController.create(context, null /* synchronizer */, floatingContentCoordinator, statusBarService, windowManager, windowManagerShellWrapper, launcherApps, taskStackListener, - uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue); + uiEventLogger, organizer, displayController, mainExecutor, mainHandler, + taskViewTransitions, syncQueue); } // diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java index 1cbad155ba7b..03df92fd8477 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java @@ -21,6 +21,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,12 +45,14 @@ import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceSession; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable; +import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; @@ -75,6 +79,8 @@ public class TaskViewTest extends ShellTestCase { HandlerExecutor mExecutor; @Mock SyncTransactionQueue mSyncQueue; + @Mock + TaskViewTransitions mTaskViewTransitions; SurfaceSession mSession; SurfaceControl mLeash; @@ -110,7 +116,7 @@ public class TaskViewTest extends ShellTestCase { return null; }).when(mSyncQueue).runInSync(any()); - mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue); + mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue); mTaskView.setListener(mExecutor, mViewListener); } @@ -123,7 +129,7 @@ public class TaskViewTest extends ShellTestCase { @Test public void testSetPendingListener_throwsException() { - TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue); + TaskView taskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue); taskView.setListener(mExecutor, mViewListener); try { taskView.setListener(mExecutor, mViewListener); @@ -144,7 +150,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testOnTaskAppeared_noSurface() { + public void testOnTaskAppeared_noSurface_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); @@ -154,7 +161,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testOnTaskAppeared_withSurface() { + public void testOnTaskAppeared_withSurface_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.surfaceCreated(mock(SurfaceHolder.class)); mTaskView.onTaskAppeared(mTaskInfo, mLeash); @@ -163,7 +171,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testSurfaceCreated_noTask() { + public void testSurfaceCreated_noTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.surfaceCreated(mock(SurfaceHolder.class)); verify(mViewListener).onInitialized(); @@ -172,7 +181,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testSurfaceCreated_withTask() { + public void testSurfaceCreated_withTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); mTaskView.surfaceCreated(mock(SurfaceHolder.class)); @@ -181,7 +191,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testSurfaceDestroyed_noTask() { + public void testSurfaceDestroyed_noTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); SurfaceHolder sh = mock(SurfaceHolder.class); mTaskView.surfaceCreated(sh); mTaskView.surfaceDestroyed(sh); @@ -190,7 +201,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testSurfaceDestroyed_withTask() { + public void testSurfaceDestroyed_withTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); SurfaceHolder sh = mock(SurfaceHolder.class); mTaskView.onTaskAppeared(mTaskInfo, mLeash); mTaskView.surfaceCreated(sh); @@ -201,7 +213,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testOnReleased() { + public void testOnReleased_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); mTaskView.surfaceCreated(mock(SurfaceHolder.class)); mTaskView.release(); @@ -211,7 +224,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testOnTaskVanished() { + public void testOnTaskVanished_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); mTaskView.surfaceCreated(mock(SurfaceHolder.class)); mTaskView.onTaskVanished(mTaskInfo); @@ -220,7 +234,8 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testOnBackPressedOnTaskRoot() { + public void testOnBackPressedOnTaskRoot_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); mTaskView.onBackPressedOnTaskRoot(mTaskInfo); @@ -228,17 +243,158 @@ public class TaskViewTest extends ShellTestCase { } @Test - public void testSetOnBackPressedOnTaskRoot() { + public void testSetOnBackPressedOnTaskRoot_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true)); } @Test - public void testUnsetOnBackPressedOnTaskRoot() { + public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); mTaskView.onTaskAppeared(mTaskInfo, mLeash); verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true)); mTaskView.onTaskVanished(mTaskInfo); verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false)); } + + @Test + public void testOnNewTask_noSurface() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + + verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); + verify(mViewListener, never()).onInitialized(); + // If there's no surface the task should be made invisible + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false)); + } + + @Test + public void testSurfaceCreated_noTask() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean()); + + verify(mViewListener).onInitialized(); + // No task, no visibility change + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testOnNewTask_withSurface() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + + verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testSurfaceCreated_withTask() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + + verify(mViewListener).onInitialized(); + verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(true)); + + mTaskView.prepareOpenAnimation(false /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true)); + } + + @Test + public void testSurfaceDestroyed_noTask() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + SurfaceHolder sh = mock(SurfaceHolder.class); + mTaskView.surfaceCreated(sh); + mTaskView.surfaceDestroyed(sh); + + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testSurfaceDestroyed_withTask() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + SurfaceHolder sh = mock(SurfaceHolder.class); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + mTaskView.surfaceCreated(sh); + reset(mViewListener); + mTaskView.surfaceDestroyed(sh); + + verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(false)); + + mTaskView.prepareHideAnimation(new SurfaceControl.Transaction()); + + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false)); + } + + @Test + public void testOnReleased() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + mTaskView.release(); + + verify(mOrganizer).removeListener(eq(mTaskView)); + verify(mViewListener).onReleased(); + verify(mTaskViewTransitions).removeTaskView(eq(mTaskView)); + } + + @Test + public void testOnTaskVanished() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + mTaskView.prepareCloseAnimation(); + + verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId)); + } + + @Test + public void testOnBackPressedOnTaskRoot() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + mTaskView.onBackPressedOnTaskRoot(mTaskInfo); + + verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId)); + } + + @Test + public void testSetOnBackPressedOnTaskRoot() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true)); + } + + @Test + public void testUnsetOnBackPressedOnTaskRoot() { + assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS); + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(), + new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct); + verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true)); + + mTaskView.prepareCloseAnimation(); + verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index c35d51ad629f..71ae5eb028a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -108,6 +108,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleData; @@ -246,6 +247,8 @@ public class BubblesTest extends SysuiTestCase { private ScreenOffAnimationController mScreenOffAnimationController; @Mock private AuthController mAuthController; + @Mock + private TaskViewTransitions mTaskViewTransitions; private TestableBubblePositioner mPositioner; @@ -344,6 +347,7 @@ public class BubblesTest extends SysuiTestCase { mock(DisplayController.class), syncExecutor, mock(Handler.class), + mTaskViewTransitions, mock(SyncTransactionQueue.class)); mBubbleController.setExpandListener(mBubbleExpandListener); spyOn(mBubbleController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index e2fce67e8b78..7b9e6a9efb3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -90,6 +90,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleData; @@ -217,6 +218,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; @Mock private ScreenOffAnimationController mScreenOffAnimationController; + @Mock + private TaskViewTransitions mTaskViewTransitions; private TestableBubblePositioner mPositioner; @@ -306,6 +309,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mock(DisplayController.class), syncExecutor, mock(Handler.class), + mTaskViewTransitions, mock(SyncTransactionQueue.class)); mBubbleController.setExpandListener(mBubbleExpandListener); spyOn(mBubbleController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 7b77cb0b5b30..80834c8130e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -23,6 +23,7 @@ import android.view.WindowManager; import com.android.internal.statusbar.IStatusBarService; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; @@ -56,11 +57,12 @@ public class TestableBubbleController extends BubbleController { DisplayController displayController, ShellExecutor shellMainExecutor, Handler shellMainHandler, + TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { super(context, data, Runnable::run, floatingContentCoordinator, dataRepository, statusBarService, windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController, - shellMainExecutor, shellMainHandler, syncQueue); + shellMainExecutor, shellMainHandler, taskViewTransitions, syncQueue); setInflateSynchronously(true); initialize(); } |