Loading libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +148 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. */ Loading @@ -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) { Loading @@ -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 */, Loading Loading @@ -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. */ Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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) Loading @@ -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(); Loading Loading @@ -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 */); } }); } } } libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java +12 −1 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -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); }); Loading libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * 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; } } libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +10 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading @@ -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); } /** Loading @@ -243,6 +246,7 @@ public class BubbleController { DisplayController displayController, ShellExecutor mainExecutor, Handler mainHandler, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; mLauncherApps = launcherApps; Loading @@ -266,6 +270,7 @@ public class BubbleController { mSavedBubbleKeysPerUser = new SparseSetArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; mSyncQueue = syncQueue; } Loading Loading @@ -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; Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +148 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. */ Loading @@ -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) { Loading @@ -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 */, Loading Loading @@ -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. */ Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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) Loading @@ -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(); Loading Loading @@ -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 */); } }); } } }
libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java +12 −1 Original line number Diff line number Diff line Loading @@ -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() { Loading @@ -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); }); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java 0 → 100644 +253 −0 Original line number Diff line number Diff line /* * 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; } }
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +10 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); Loading @@ -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); } /** Loading @@ -243,6 +246,7 @@ public class BubbleController { DisplayController displayController, ShellExecutor mainExecutor, Handler mainHandler, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { mContext = context; mLauncherApps = launcherApps; Loading @@ -266,6 +270,7 @@ public class BubbleController { mSavedBubbleKeysPerUser = new SparseSetArray<>(); mBubbleIconFactory = new BubbleIconFactory(context); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; mSyncQueue = syncQueue; } Loading Loading @@ -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; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading