• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.taskview;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.view.WindowManager.TRANSIT_CHANGE;
22 import static android.view.WindowManager.TRANSIT_CLOSE;
23 import static android.view.WindowManager.TRANSIT_NONE;
24 import static android.view.WindowManager.TRANSIT_OPEN;
25 import static android.view.WindowManager.TRANSIT_TO_BACK;
26 import static android.view.WindowManager.TRANSIT_TO_FRONT;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.ActivityOptions;
32 import android.app.PendingIntent;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.LauncherApps;
36 import android.content.pm.ShortcutInfo;
37 import android.graphics.Rect;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.util.ArrayMap;
41 import android.util.Slog;
42 import android.view.SurfaceControl;
43 import android.view.WindowManager;
44 import android.window.TransitionInfo;
45 import android.window.TransitionRequestInfo;
46 import android.window.WindowContainerToken;
47 import android.window.WindowContainerTransaction;
48 
49 import androidx.annotation.VisibleForTesting;
50 
51 import com.android.wm.shell.Flags;
52 import com.android.wm.shell.ShellTaskOrganizer;
53 import com.android.wm.shell.common.SyncTransactionQueue;
54 import com.android.wm.shell.shared.TransitionUtil;
55 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
56 import com.android.wm.shell.transition.Transitions;
57 
58 import java.util.ArrayList;
59 import java.util.Map;
60 import java.util.Objects;
61 import java.util.WeakHashMap;
62 import java.util.concurrent.Executor;
63 
64 /**
65  * Handles Shell Transitions that involve TaskView tasks.
66  */
67 public class TaskViewTransitions implements Transitions.TransitionHandler, TaskViewController {
68     static final String TAG = "TaskViewTransitions";
69 
70     /**
71      * Map of {@link TaskViewTaskController} to {@link TaskViewRepository.TaskViewState}.
72      * <p>
73      * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and
74      * manages its lifecycle.
75      * Only keep a weak reference to the controller instance here to allow for it to be cleaned
76      * up when its TaskView is no longer used.
77      */
78     private final Map<TaskViewTaskController, TaskViewRepository.TaskViewState> mTaskViews;
79     private final TaskViewRepository mTaskViewRepo;
80     private final ArrayList<PendingTransition> mPending = new ArrayList<>();
81     private final Transitions mTransitions;
82     private final boolean[] mRegistered = new boolean[]{false};
83     private final ShellTaskOrganizer mTaskOrganizer;
84     private final Executor mShellExecutor;
85     private final SyncTransactionQueue mSyncQueue;
86 
87     /** A temp transaction used for quick things. */
88     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
89 
90     /**
91      * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
92      * in-flight (collecting) at a time (because otherwise, the operations could get merged into
93      * a single transition). So, keep a queue here until we add a queue in server-side.
94      */
95     @VisibleForTesting
96     static class PendingTransition {
97         final @WindowManager.TransitionType int mType;
98         final WindowContainerTransaction mWct;
99         final @NonNull TaskViewTaskController mTaskView;
100         ExternalTransition mExternalTransition;
101         IBinder mClaimed;
102 
103         /**
104          * This is needed because arbitrary activity launches can still "intrude" into any
105          * transition since `startActivity` is a synchronous call. Once that is solved, we can
106          * remove this.
107          */
108         final IBinder mLaunchCookie;
109 
PendingTransition(@indowManager.TransitionType int type, @Nullable WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @Nullable IBinder launchCookie)110         PendingTransition(@WindowManager.TransitionType int type,
111                 @Nullable WindowContainerTransaction wct,
112                 @NonNull TaskViewTaskController taskView,
113                 @Nullable IBinder launchCookie) {
114             mType = type;
115             mWct = wct;
116             mTaskView = taskView;
117             mLaunchCookie = launchCookie;
118         }
119     }
120 
TaskViewTransitions(Transitions transitions, TaskViewRepository repository, ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue)121     public TaskViewTransitions(Transitions transitions, TaskViewRepository repository,
122             ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue) {
123         mTransitions = transitions;
124         mTaskOrganizer = taskOrganizer;
125         mShellExecutor = taskOrganizer.getExecutor();
126         mSyncQueue = syncQueue;
127         if (useRepo()) {
128             mTaskViews = null;
129         } else if (Flags.enableTaskViewControllerCleanup()) {
130             mTaskViews = new WeakHashMap<>();
131         } else {
132             mTaskViews = new ArrayMap<>();
133         }
134         mTaskViewRepo = repository;
135         // Defer registration until the first TaskView because we want this to be the "first" in
136         // priority when handling requests.
137         // TODO(210041388): register here once we have an explicit ordering mechanism.
138     }
139 
140     /** @return whether the shared taskview repository is being used. */
useRepo()141     public static boolean useRepo() {
142         return Flags.taskViewRepository() || Flags.enableBubbleAnything();
143     }
144 
getRepository()145     public TaskViewRepository getRepository() {
146         return mTaskViewRepo;
147     }
148 
149     @Override
registerTaskView(TaskViewTaskController tv)150     public void registerTaskView(TaskViewTaskController tv) {
151         synchronized (mRegistered) {
152             if (!mRegistered[0]) {
153                 mRegistered[0] = true;
154                 mTransitions.addHandler(this);
155             }
156         }
157         if (useRepo()) {
158             mTaskViewRepo.add(tv);
159         } else {
160             mTaskViews.put(tv, new TaskViewRepository.TaskViewState(null));
161         }
162     }
163 
164     @Override
unregisterTaskView(TaskViewTaskController tv)165     public void unregisterTaskView(TaskViewTaskController tv) {
166         if (useRepo()) {
167             mTaskViewRepo.remove(tv);
168         } else {
169             mTaskViews.remove(tv);
170         }
171         // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
172     }
173 
174     @Override
isUsingShellTransitions()175     public boolean isUsingShellTransitions() {
176         return mTransitions.isRegistered();
177     }
178 
179     /**
180      * Starts a transition outside of the handler associated with {@link TaskViewTransitions}.
181      */
startInstantTransition(@indowManager.TransitionType int type, WindowContainerTransaction wct)182     public void startInstantTransition(@WindowManager.TransitionType int type,
183             WindowContainerTransaction wct) {
184         mTransitions.startTransition(type, wct, null);
185     }
186 
187     /**
188      * Starts or queues an "external" runnable into the pending queue. This means it will run
189      * in order relative to the local transitions.
190      *
191      * The external operation *must* call {@link #onExternalDone} once it has finished.
192      *
193      * In practice, the external is usually another transition on a different handler.
194      */
enqueueExternal(@onNull TaskViewTaskController taskView, ExternalTransition ext)195     public void enqueueExternal(@NonNull TaskViewTaskController taskView, ExternalTransition ext) {
196         final PendingTransition pending = new PendingTransition(
197                 TRANSIT_NONE, null /* wct */, taskView, null /* cookie */);
198         pending.mExternalTransition = ext;
199         mPending.add(pending);
200         startNextTransition();
201     }
202 
203     /**
204      * An external transition run in this "queue" is required to call this once it becomes ready.
205      */
onExternalDone(IBinder key)206     public void onExternalDone(IBinder key) {
207         final PendingTransition pending = findPending(key);
208         if (pending == null) return;
209         mPending.remove(pending);
210         startNextTransition();
211     }
212 
213     /**
214      * Looks through the pending transitions for a opening transaction that matches the provided
215      * `taskView`.
216      *
217      * @param taskView the pending transition should be for this.
218      */
219     @VisibleForTesting
findPendingOpeningTransition(TaskViewTaskController taskView)220     PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
221         for (int i = mPending.size() - 1; i >= 0; --i) {
222             if (mPending.get(i).mTaskView != taskView) continue;
223             if (mPending.get(i).mExternalTransition != null) continue;
224             if (TransitionUtil.isOpeningType(mPending.get(i).mType)) {
225                 return mPending.get(i);
226             }
227         }
228         return null;
229     }
230 
231     /**
232      * Looks through the pending transitions for one matching `taskView`.
233      *
234      * @param taskView the pending transition should be for this.
235      * @param type     the type of transition it's looking for
236      */
findPending(TaskViewTaskController taskView, int type)237     PendingTransition findPending(TaskViewTaskController taskView, int type) {
238         for (int i = mPending.size() - 1; i >= 0; --i) {
239             if (mPending.get(i).mTaskView != taskView) continue;
240             if (mPending.get(i).mExternalTransition != null) continue;
241             if (mPending.get(i).mType == type) {
242                 return mPending.get(i);
243             }
244         }
245         return null;
246     }
247 
findPending(IBinder claimed)248     private PendingTransition findPending(IBinder claimed) {
249         for (int i = 0; i < mPending.size(); ++i) {
250             if (mPending.get(i).mClaimed != claimed) continue;
251             return mPending.get(i);
252         }
253         return null;
254     }
255 
256     /** @return whether there are pending transitions on TaskViews. */
hasPending()257     public boolean hasPending() {
258         return !mPending.isEmpty();
259     }
260 
261     @Override
handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)262     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
263             @Nullable TransitionRequestInfo request) {
264         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
265         if (triggerTask == null) {
266             return null;
267         }
268         final TaskViewTaskController taskView = findTaskView(triggerTask);
269         if (taskView == null) return null;
270         // Opening types should all be initiated by shell
271         if (!TransitionUtil.isClosingType(request.getType())) return null;
272         PendingTransition pending = new PendingTransition(request.getType(), null,
273                 taskView, null /* cookie */);
274         pending.mClaimed = transition;
275         mPending.add(pending);
276         return new WindowContainerTransaction();
277     }
278 
findTaskView(ActivityManager.RunningTaskInfo taskInfo)279     private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
280         if (useRepo()) {
281             final TaskViewRepository.TaskViewState state = mTaskViewRepo.byToken(taskInfo.token);
282             return state != null ? state.getTaskView() : null;
283         }
284         if (Flags.enableTaskViewControllerCleanup()) {
285             for (TaskViewTaskController controller : mTaskViews.keySet()) {
286                 if (controller.getTaskInfo() == null) continue;
287                 if (taskInfo.token.equals(controller.getTaskInfo().token)) {
288                     return controller;
289                 }
290             }
291         } else {
292             ArrayMap<TaskViewTaskController, TaskViewRepository.TaskViewState> taskViews =
293                     (ArrayMap<TaskViewTaskController, TaskViewRepository.TaskViewState>) mTaskViews;
294             for (int i = 0; i < taskViews.size(); ++i) {
295                 if (taskViews.keyAt(i).getTaskInfo() == null) continue;
296                 if (taskInfo.token.equals(taskViews.keyAt(i).getTaskInfo().token)) {
297                     return taskViews.keyAt(i);
298                 }
299             }
300         }
301         return null;
302     }
303 
304     /** Returns true if the given {@code taskInfo} belongs to a task view. */
isTaskViewTask(ActivityManager.RunningTaskInfo taskInfo)305     public boolean isTaskViewTask(ActivityManager.RunningTaskInfo taskInfo) {
306         return findTaskView(taskInfo) != null;
307     }
308 
prepareActivityOptions(ActivityOptions options, Rect launchBounds, @NonNull TaskViewTaskController destination)309     private void prepareActivityOptions(ActivityOptions options, Rect launchBounds,
310             @NonNull TaskViewTaskController destination) {
311         final Binder launchCookie = new Binder();
312         mShellExecutor.execute(() -> {
313             mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, destination);
314         });
315         options.setLaunchBounds(launchBounds);
316         options.setLaunchCookie(launchCookie);
317         options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
318         options.setRemoveWithTaskOrganizer(true);
319     }
320 
321     @Override
startShortcutActivity(@onNull TaskViewTaskController destination, @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)322     public void startShortcutActivity(@NonNull TaskViewTaskController destination,
323             @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
324             @Nullable Rect launchBounds) {
325         prepareActivityOptions(options, launchBounds, destination);
326         final Context context = destination.getContext();
327         if (isUsingShellTransitions()) {
328             mShellExecutor.execute(() -> {
329                 final WindowContainerTransaction wct = new WindowContainerTransaction();
330                 wct.startShortcut(context.getPackageName(), shortcut, options.toBundle());
331                 startTaskView(wct, destination, options.getLaunchCookie());
332             });
333             return;
334         }
335         try {
336             LauncherApps service = context.getSystemService(LauncherApps.class);
337             service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
338         } catch (Exception e) {
339             throw new RuntimeException(e);
340         }
341     }
342 
343     @Override
startActivity(@onNull TaskViewTaskController destination, @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)344     public void startActivity(@NonNull TaskViewTaskController destination,
345             @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
346             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
347         prepareActivityOptions(options, launchBounds, destination);
348         if (isUsingShellTransitions()) {
349             mShellExecutor.execute(() -> {
350                 WindowContainerTransaction wct = new WindowContainerTransaction();
351                 wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
352                 startTaskView(wct, destination, options.getLaunchCookie());
353             });
354             return;
355         }
356         try {
357             pendingIntent.send(destination.getContext(), 0 /* code */, fillInIntent,
358                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
359                     options.toBundle());
360         } catch (Exception e) {
361             throw new RuntimeException(e);
362         }
363     }
364 
365     @Override
startRootTask(@onNull TaskViewTaskController destination, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, @Nullable WindowContainerTransaction wct)366     public void startRootTask(@NonNull TaskViewTaskController destination,
367             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
368             @Nullable WindowContainerTransaction wct) {
369         if (wct == null) {
370             wct = new WindowContainerTransaction();
371         }
372         // This method skips the regular flow where an activity task is launched as part of a new
373         // transition in taskview and then transition is intercepted using the launchcookie.
374         // The task here is already created and running, it just needs to be reparented, resized
375         // and tracked correctly inside taskview. Which is done by calling
376         // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
377         // transaction.
378         prepareOpenAnimation(destination, true /* newTask */, mTransaction /* startTransaction */,
379                 null /* finishTransaction */, taskInfo, leash, wct);
380         mTransaction.apply();
381         mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
382     }
383 
384     @VisibleForTesting
startTaskView(@onNull WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie)385     void startTaskView(@NonNull WindowContainerTransaction wct,
386             @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
387         updateVisibilityState(taskView, true /* visible */);
388         mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie));
389         startNextTransition();
390     }
391 
392     @Override
removeTaskView(@onNull TaskViewTaskController taskView, @Nullable WindowContainerToken taskToken)393     public void removeTaskView(@NonNull TaskViewTaskController taskView,
394             @Nullable WindowContainerToken taskToken) {
395         final WindowContainerToken token = taskToken != null ? taskToken : taskView.getTaskToken();
396         if (token == null) {
397             // We don't have a task yet, so just clean up records
398             if (!Flags.enableTaskViewControllerCleanup()) {
399                 // Call to remove task before we have one, do nothing
400                 Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
401                 return;
402             }
403             unregisterTaskView(taskView);
404             return;
405         }
406         final WindowContainerTransaction wct = new WindowContainerTransaction();
407         wct.removeTask(token);
408         updateVisibilityState(taskView, false /* visible */);
409         mShellExecutor.execute(() -> {
410             mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
411             startNextTransition();
412         });
413     }
414 
415     @Override
moveTaskViewToFullscreen(@onNull TaskViewTaskController taskView)416     public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
417         final WindowContainerToken taskToken = taskView.getTaskToken();
418         if (taskToken == null) return;
419         final WindowContainerTransaction wct = new WindowContainerTransaction();
420         wct.setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED);
421         wct.setAlwaysOnTop(taskToken, false);
422         mShellExecutor.execute(() -> {
423             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskToken, false);
424             mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
425             startNextTransition();
426             taskView.notifyTaskRemovalStarted(taskView.getTaskInfo());
427         });
428     }
429 
430     @Override
setTaskViewVisible(TaskViewTaskController taskView, boolean visible)431     public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
432         setTaskViewVisible(taskView, visible, false /* reorder */);
433     }
434 
435     /**
436      * Starts a new transition to make the given {@code taskView} visible and optionally
437      * reordering it.
438      *
439      * @param reorder  whether to reorder the task or not. If this is {@code true}, the task will
440      *                 be reordered as per the given {@code visible}. For {@code visible = true},
441      *                 task will be reordered to top. For {@code visible = false}, task will be
442      *                 reordered to the bottom
443      */
setTaskViewVisible(TaskViewTaskController taskView, boolean visible, boolean reorder)444     public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible,
445             boolean reorder) {
446         final TaskViewRepository.TaskViewState state = useRepo()
447                 ? mTaskViewRepo.byTaskView(taskView)
448                 : mTaskViews.get(taskView);
449         if (state == null) return;
450         if (state.mVisible == visible) return;
451         if (taskView.getTaskInfo() == null) {
452             // Nothing to update, task is not yet available
453             return;
454         }
455         state.mVisible = visible;
456         final WindowContainerTransaction wct = new WindowContainerTransaction();
457         wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
458         wct.setBounds(taskView.getTaskInfo().token, state.mBounds);
459         if (reorder) {
460             wct.reorder(taskView.getTaskInfo().token, visible /* onTop */);
461         }
462         PendingTransition pending = new PendingTransition(
463                 visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */);
464         mPending.add(pending);
465         startNextTransition();
466         // visibility is reported in transition.
467     }
468 
469     /** Starts a new transition to reorder the given {@code taskView}'s task. */
reorderTaskViewTask(TaskViewTaskController taskView, boolean onTop)470     public void reorderTaskViewTask(TaskViewTaskController taskView, boolean onTop) {
471         final TaskViewRepository.TaskViewState state = useRepo()
472                 ? mTaskViewRepo.byTaskView(taskView)
473                 : mTaskViews.get(taskView);
474         if (state == null) return;
475         if (taskView.getTaskInfo() == null) {
476             // Nothing to update, task is not yet available
477             return;
478         }
479         final WindowContainerTransaction wct = new WindowContainerTransaction();
480         wct.reorder(taskView.getTaskInfo().token, onTop /* onTop */);
481         PendingTransition pending = new PendingTransition(
482                 onTop ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */);
483         mPending.add(pending);
484         startNextTransition();
485         // visibility is reported in transition.
486     }
487 
updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen)488     void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) {
489         if (useRepo()) return;
490         final TaskViewRepository.TaskViewState state = mTaskViews.get(taskView);
491         if (state == null) return;
492         state.mBounds.set(boundsOnScreen);
493     }
494 
updateVisibilityState(TaskViewTaskController taskView, boolean visible)495     void updateVisibilityState(TaskViewTaskController taskView, boolean visible) {
496         final TaskViewRepository.TaskViewState state = useRepo()
497                 ? mTaskViewRepo.byTaskView(taskView)
498                 : mTaskViews.get(taskView);
499         if (state == null) return;
500         state.mVisible = visible;
501     }
502 
503     @Override
setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen)504     public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
505         if (taskView.getTaskToken() == null) {
506             return;
507         }
508 
509         if (isUsingShellTransitions()) {
510             mShellExecutor.execute(() -> {
511                 // Sync Transactions can't operate simultaneously with shell transition collection.
512                 setTaskBoundsInTransition(taskView, boundsOnScreen);
513             });
514             return;
515         }
516 
517         WindowContainerTransaction wct = new WindowContainerTransaction();
518         wct.setBounds(taskView.getTaskToken(), boundsOnScreen);
519         mSyncQueue.queue(wct);
520     }
521 
setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen)522     private void setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen) {
523         final TaskViewRepository.TaskViewState state = useRepo()
524                 ? mTaskViewRepo.byTaskView(taskView)
525                 : mTaskViews.get(taskView);
526         if (state == null || Objects.equals(boundsOnScreen, state.mBounds)) {
527             return;
528         }
529         state.mBounds.set(boundsOnScreen);
530         if (!state.mVisible) {
531             // Task view isn't visible, the bounds will next visibility update.
532             return;
533         }
534         if (hasPending()) {
535             // There is already a transition in-flight, the window bounds will be set in
536             // prepareOpenAnimation.
537             return;
538         }
539         WindowContainerTransaction wct = new WindowContainerTransaction();
540         wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen);
541         mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
542         startNextTransition();
543     }
544 
startNextTransition()545     private void startNextTransition() {
546         if (mPending.isEmpty()) return;
547         final PendingTransition pending = mPending.get(0);
548         if (pending.mClaimed != null) {
549             // Wait for this to start animating.
550             return;
551         }
552         if (pending.mExternalTransition != null) {
553             pending.mClaimed = pending.mExternalTransition.start();
554         } else {
555             pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
556         }
557     }
558 
559     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @NonNull SurfaceControl.Transaction finishTransaction)560     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
561             @NonNull SurfaceControl.Transaction finishTransaction) {
562         if (!aborted) return;
563         final PendingTransition pending = findPending(transition);
564         if (pending == null) return;
565         mPending.remove(pending);
566         startNextTransition();
567     }
568 
569     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)570     public boolean startAnimation(@NonNull IBinder transition,
571             @NonNull TransitionInfo info,
572             @NonNull SurfaceControl.Transaction startTransaction,
573             @NonNull SurfaceControl.Transaction finishTransaction,
574             @NonNull Transitions.TransitionFinishCallback finishCallback) {
575         final PendingTransition pending = findPending(transition);
576         if (pending != null) {
577             mPending.remove(pending);
578         }
579         if (useRepo() ? mTaskViewRepo.isEmpty() : mTaskViews.isEmpty()) {
580             if (pending != null) {
581                 Slog.e(TAG, "Pending taskview transition but no task-views");
582             }
583             return false;
584         }
585         boolean stillNeedsMatchingLaunch = pending != null && pending.mLaunchCookie != null;
586         int changesHandled = 0;
587         WindowContainerTransaction wct = null;
588         for (int i = 0; i < info.getChanges().size(); ++i) {
589             final TransitionInfo.Change chg = info.getChanges().get(i);
590             final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo();
591             if (taskInfo == null) continue;
592             if (TransitionUtil.isClosingType(chg.getMode())) {
593                 final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
594                 TaskViewTaskController tv = findTaskView(taskInfo);
595                 if (tv == null && !isHide) {
596                     // TaskView can be null when closing
597                     changesHandled++;
598                     continue;
599                 }
600                 if (tv == null) {
601                     if (pending != null) {
602                         Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
603                                 + "shouldn't happen, so there may be a visual artifact: "
604                                 + taskInfo.taskId);
605                     }
606                     continue;
607                 }
608                 if (isHide) {
609                     if (pending != null && pending.mType == TRANSIT_TO_BACK) {
610                         // TO_BACK is only used when setting the task view visibility immediately,
611                         // so in that case we can also hide the surface immediately
612                         startTransaction.hide(chg.getLeash());
613                     }
614                     tv.prepareHideAnimation(finishTransaction);
615                 } else {
616                     tv.prepareCloseAnimation();
617                 }
618                 changesHandled++;
619             } else if (TransitionUtil.isOpeningType(chg.getMode())) {
620                 boolean isNewInTaskView = false;
621                 TaskViewTaskController tv;
622                 if (chg.getMode() == TRANSIT_OPEN) {
623                     isNewInTaskView = true;
624                     if (pending == null || !taskInfo.containsLaunchCookie(pending.mLaunchCookie)) {
625                         Slog.e(TAG, "Found a launching TaskView in the wrong transition. All "
626                                 + "TaskView launches should be initiated by shell and in their "
627                                 + "own transition: " + taskInfo.taskId);
628                         continue;
629                     }
630                     stillNeedsMatchingLaunch = false;
631                     tv = pending.mTaskView;
632                 } else {
633                     tv = findTaskView(taskInfo);
634                     if (tv == null && pending != null) {
635                         if (BubbleAnythingFlagHelper.enableCreateAnyBubble()
636                                 && chg.getMode() == TRANSIT_TO_FRONT
637                                 && pending.mTaskView.getPendingInfo() != null
638                                 && pending.mTaskView.getPendingInfo().taskId == taskInfo.taskId) {
639                             // In this case an existing task, not currently in TaskView, is
640                             // brought to the front to be moved into TaskView. This is still
641                             // "new" from TaskView's perspective. (e.g. task being moved into a
642                             // bubble)
643                             isNewInTaskView = true;
644                             stillNeedsMatchingLaunch = false;
645                             tv = pending.mTaskView;
646                         } else {
647                             Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. "
648                                     + "This shouldn't happen, so there may be a visual "
649                                     + "artifact: " + taskInfo.taskId);
650                         }
651                     }
652                     if (tv == null) continue;
653                 }
654                 if (wct == null) wct = new WindowContainerTransaction();
655                 prepareOpenAnimation(tv, isNewInTaskView, startTransaction, finishTransaction,
656                         taskInfo, chg.getLeash(), wct);
657                 changesHandled++;
658             } else if (chg.getMode() == TRANSIT_CHANGE) {
659                 TaskViewTaskController tv = findTaskView(taskInfo);
660                 if (tv == null) {
661                     if (pending != null) {
662                         Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This "
663                                 + "shouldn't happen, so there may be a visual artifact: "
664                                 + taskInfo.taskId);
665                     }
666                     continue;
667                 }
668                 final Rect boundsOnScreen = tv.prepareOpen(chg.getTaskInfo(), chg.getLeash());
669                 if (boundsOnScreen != null) {
670                     if (wct == null) wct = new WindowContainerTransaction();
671                     updateBounds(tv, boundsOnScreen, startTransaction, finishTransaction,
672                             chg.getTaskInfo(), chg.getLeash(), wct);
673                 } else {
674                     startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
675                     finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
676                             .setPosition(chg.getLeash(), 0, 0);
677                 }
678                 changesHandled++;
679             }
680         }
681         if (stillNeedsMatchingLaunch) {
682             Slog.w(TAG, "Expected a TaskView launch in this transition but didn't get one, "
683                     + "cleaning up the task view");
684             // Didn't find a task so the task must have never launched
685             pending.mTaskView.setTaskNotFound();
686         } else if (wct == null && pending == null && changesHandled != info.getChanges().size()) {
687             // Just some house-keeping, let another handler animate.
688             return false;
689         }
690         // No animation, just show it immediately.
691         startTransaction.apply();
692         finishCallback.onTransitionFinished(wct);
693         startNextTransition();
694         return true;
695     }
696 
697     @VisibleForTesting
prepareOpenAnimation(TaskViewTaskController taskView, final boolean newTask, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)698     public void prepareOpenAnimation(TaskViewTaskController taskView,
699             final boolean newTask,
700             SurfaceControl.Transaction startTransaction,
701             SurfaceControl.Transaction finishTransaction,
702             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
703             WindowContainerTransaction wct) {
704         final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
705         if (boundsOnScreen != null) {
706             updateBounds(taskView, boundsOnScreen, startTransaction, finishTransaction, taskInfo,
707                     leash, wct);
708         } else {
709             // The surface has already been destroyed before the task has appeared,
710             // so go ahead and hide the task entirely
711             wct.setHidden(taskInfo.token, true /* hidden */);
712             updateVisibilityState(taskView, false /* visible */);
713             // listener callback is below
714         }
715         if (newTask) {
716             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true /* intercept */);
717         }
718 
719         if (taskInfo.taskDescription != null) {
720             int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
721             taskView.setResizeBgColor(startTransaction, backgroundColor);
722         }
723 
724         // After the embedded task has appeared, set it to non-trimmable. This is important
725         // to prevent recents from trimming and removing the embedded task.
726         wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
727 
728         taskView.notifyAppeared(newTask);
729     }
730 
731     /**
732      * Updates bounds for the task view during an unfold transition.
733      *
734      * @return true if the task was found and a transition for this task is pending. false
735      * otherwise.
736      */
updateBoundsForUnfold(Rect bounds, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)737     public boolean updateBoundsForUnfold(Rect bounds, SurfaceControl.Transaction startTransaction,
738             SurfaceControl.Transaction finishTransaction,
739             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
740         final TaskViewTaskController taskView = findTaskView(taskInfo);
741         if (taskView == null) {
742             return false;
743         }
744 
745         final PendingTransition pendingTransition = findPending(taskView, TRANSIT_CHANGE);
746         if (pendingTransition == null) {
747             return false;
748         }
749 
750         mPending.remove(pendingTransition);
751 
752         // reparent the task under the task view surface and set the bounds on it
753         startTransaction.reparent(leash, taskView.getSurfaceControl())
754                 .setPosition(leash, 0, 0)
755                 .setWindowCrop(leash, bounds.width(), bounds.height())
756                 .show(leash);
757         // the finish transaction would reparent the task back to the transition root, so reparent
758         // it again to the task view surface
759         finishTransaction.reparent(leash, taskView.getSurfaceControl())
760                 .setPosition(leash, 0, 0)
761                 .setWindowCrop(leash, bounds.width(), bounds.height());
762         if (useRepo()) {
763             final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
764             if (state != null) {
765                 state.mBounds.set(bounds);
766             }
767         } else {
768             updateBoundsState(taskView, bounds);
769         }
770         return true;
771     }
772 
updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)773     private void updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen,
774             SurfaceControl.Transaction startTransaction,
775             SurfaceControl.Transaction finishTransaction,
776             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
777             WindowContainerTransaction wct) {
778         final SurfaceControl tvSurface = taskView.getSurfaceControl();
779         // Surface is ready, so just reparent the task to this surface control
780         startTransaction.reparent(leash, tvSurface)
781                 .show(leash);
782         // Also reparent on finishTransaction since the finishTransaction will reparent back
783         // to its "original" parent by default.
784         if (finishTransaction != null) {
785             finishTransaction.reparent(leash, tvSurface)
786                     .setPosition(leash, 0, 0)
787                     .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
788         }
789         if (useRepo()) {
790             final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
791             if (state != null) {
792                 state.mBounds.set(boundsOnScreen);
793                 state.mVisible = true;
794             }
795         } else {
796             updateBoundsState(taskView, boundsOnScreen);
797             updateVisibilityState(taskView, true /* visible */);
798         }
799         wct.setBounds(taskInfo.token, boundsOnScreen);
800         taskView.applyCaptionInsetsIfNeeded();
801     }
802 
803     /** Interface for running an external transition in this object's pending queue. */
804     public interface ExternalTransition {
805         /** Starts a transition and returns an identifying key for lookup. */
start()806         IBinder start();
807     }
808 }
809