1 /*
2  * Copyright (C) 2020 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 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.app.ActivityOptions;
25 import android.app.PendingIntent;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.LauncherApps;
30 import android.content.pm.ShortcutInfo;
31 import android.graphics.Rect;
32 import android.os.Binder;
33 import android.util.CloseGuard;
34 import android.util.Slog;
35 import android.view.SurfaceControl;
36 import android.view.WindowInsets;
37 import android.window.WindowContainerToken;
38 import android.window.WindowContainerTransaction;
39 
40 import com.android.wm.shell.ShellTaskOrganizer;
41 import com.android.wm.shell.common.SyncTransactionQueue;
42 
43 import java.io.PrintWriter;
44 import java.util.concurrent.Executor;
45 
46 /**
47  * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
48  * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
49  *
50  * The reverse communication is done via the {@link TaskViewBase} interface.
51  */
52 public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
53 
54     private static final String TAG = TaskViewTaskController.class.getSimpleName();
55 
56     private final CloseGuard mGuard = new CloseGuard();
57     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
58     /** Used to inset the activity content to allow space for a caption bar. */
59     private final Binder mCaptionInsetsOwner = new Binder();
60     private final ShellTaskOrganizer mTaskOrganizer;
61     private final Executor mShellExecutor;
62     private final SyncTransactionQueue mSyncQueue;
63     private final TaskViewTransitions mTaskViewTransitions;
64     private final Context mContext;
65 
66     /**
67      * There could be a situation where we have task info and receive
68      * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the
69      * activity might fail to open, and in this case we need to clean up the task view / notify
70      * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared
71      * in this situation to allow us to notify listeners correctly if the task failed to open.
72      */
73     private ActivityManager.RunningTaskInfo mPendingInfo;
74     private TaskViewBase mTaskViewBase;
75     protected ActivityManager.RunningTaskInfo mTaskInfo;
76     private WindowContainerToken mTaskToken;
77     private SurfaceControl mTaskLeash;
78     /* Indicates that the task we attempted to launch in the task view failed to launch. */
79     private boolean mTaskNotFound;
80     private boolean mSurfaceCreated;
81     private SurfaceControl mSurfaceControl;
82     private boolean mIsInitialized;
83     private boolean mNotifiedForInitialized;
84     private boolean mHideTaskWithSurface = true;
85     private TaskView.Listener mListener;
86     private Executor mListenerExecutor;
87     private Rect mCaptionInsets;
88 
TaskViewTaskController(Context context, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue)89     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
90             TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
91         mContext = context;
92         mTaskOrganizer = organizer;
93         mShellExecutor = organizer.getExecutor();
94         mSyncQueue = syncQueue;
95         mTaskViewTransitions = taskViewTransitions;
96         mShellExecutor.execute(() -> {
97             if (mTaskViewTransitions != null) {
98                 mTaskViewTransitions.addTaskView(this);
99             }
100         });
101         mGuard.open("release");
102     }
103 
104     /**
105      * Specifies if the task should be hidden when the surface is destroyed.
106      * <p>This is {@code true} by default.
107      *
108      * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
109      *                            surface is destroyed, {@code true} otherwise.
110      */
setHideTaskWithSurface(boolean hideTaskWithSurface)111     public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
112         // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
113         // are moved to a window in SystemUI in auto.
114         mHideTaskWithSurface = hideTaskWithSurface;
115     }
116 
getSurfaceControl()117     SurfaceControl getSurfaceControl() {
118         return mSurfaceControl;
119     }
120 
121     /**
122      * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
123      * task related changes and getting the current bounds.
124      */
setTaskViewBase(TaskViewBase taskViewBase)125     public void setTaskViewBase(TaskViewBase taskViewBase) {
126         mTaskViewBase = taskViewBase;
127     }
128 
129     /**
130      * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
131      */
isInitialized()132     public boolean isInitialized() {
133         return mIsInitialized;
134     }
135 
136     /** Until all users are converted, we may have mixed-use (eg. Car). */
isUsingShellTransitions()137     public boolean isUsingShellTransitions() {
138         return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
139     }
140 
141     /**
142      * Only one listener may be set on the view, throws an exception otherwise.
143      */
setListener(@onNull Executor executor, TaskView.Listener listener)144     void setListener(@NonNull Executor executor, TaskView.Listener listener) {
145         if (mListener != null) {
146             throw new IllegalStateException(
147                     "Trying to set a listener when one has already been set");
148         }
149         mListener = listener;
150         mListenerExecutor = executor;
151     }
152 
153     /**
154      * Launch an activity represented by {@link ShortcutInfo}.
155      * <p>The owner of this container must be allowed to access the shortcut information,
156      * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
157      *
158      * @param shortcut the shortcut used to launch the activity.
159      * @param options options for the activity.
160      * @param launchBounds the bounds (window size and position) that the activity should be
161      *                     launched in, in pixels and in screen coordinates.
162      */
startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)163     public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
164             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
165         prepareActivityOptions(options, launchBounds);
166         LauncherApps service = mContext.getSystemService(LauncherApps.class);
167         if (isUsingShellTransitions()) {
168             mShellExecutor.execute(() -> {
169                 final WindowContainerTransaction wct = new WindowContainerTransaction();
170                 wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
171                 mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
172             });
173             return;
174         }
175         try {
176             service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
177         } catch (Exception e) {
178             throw new RuntimeException(e);
179         }
180     }
181 
182     /**
183      * Launch a new activity.
184      *
185      * @param pendingIntent Intent used to launch an activity.
186      * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
187      * @param options options for the activity.
188      * @param launchBounds the bounds (window size and position) that the activity should be
189      *                     launched in, in pixels and in screen coordinates.
190      */
startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)191     public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
192             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
193         prepareActivityOptions(options, launchBounds);
194         if (isUsingShellTransitions()) {
195             mShellExecutor.execute(() -> {
196                 WindowContainerTransaction wct = new WindowContainerTransaction();
197                 wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
198                 mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
199             });
200             return;
201         }
202         try {
203             pendingIntent.send(mContext, 0 /* code */, fillInIntent,
204                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
205                     options.toBundle());
206         } catch (Exception e) {
207             throw new RuntimeException(e);
208         }
209     }
210 
prepareActivityOptions(ActivityOptions options, Rect launchBounds)211     private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
212         final Binder launchCookie = new Binder();
213         mShellExecutor.execute(() -> {
214             mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
215         });
216         options.setLaunchBounds(launchBounds);
217         options.setLaunchCookie(launchCookie);
218         options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
219         options.setRemoveWithTaskOrganizer(true);
220     }
221 
222     /**
223      * Release this container if it is initialized.
224      */
release()225     public void release() {
226         performRelease();
227     }
228 
229     @Override
finalize()230     protected void finalize() throws Throwable {
231         try {
232             if (mGuard != null) {
233                 mGuard.warnIfOpen();
234                 performRelease();
235             }
236         } finally {
237             super.finalize();
238         }
239     }
240 
performRelease()241     private void performRelease() {
242         mShellExecutor.execute(() -> {
243             if (mTaskViewTransitions != null) {
244                 mTaskViewTransitions.removeTaskView(this);
245             }
246             mTaskOrganizer.removeListener(this);
247             resetTaskInfo();
248         });
249         mGuard.close();
250         mIsInitialized = false;
251         notifyReleased();
252     }
253 
254     /** Called when the {@link TaskViewTaskController} has been released. */
notifyReleased()255     protected void notifyReleased() {
256         if (mListener != null && mNotifiedForInitialized) {
257             mListenerExecutor.execute(() -> {
258                 mListener.onReleased();
259             });
260             mNotifiedForInitialized = false;
261         }
262     }
263 
resetTaskInfo()264     private void resetTaskInfo() {
265         mTaskInfo = null;
266         mTaskToken = null;
267         mTaskLeash = null;
268         mPendingInfo = null;
269         mTaskNotFound = false;
270     }
271 
272     /** This method shouldn't be called when shell transitions are enabled. */
updateTaskVisibility()273     private void updateTaskVisibility() {
274         boolean visible = mSurfaceCreated;
275         if (!visible && !mHideTaskWithSurface) {
276             return;
277         }
278         WindowContainerTransaction wct = new WindowContainerTransaction();
279         wct.setHidden(mTaskToken, !visible /* hidden */);
280         if (!visible) {
281             wct.reorder(mTaskToken, false /* onTop */);
282         }
283         mSyncQueue.queue(wct);
284         if (mListener == null) {
285             return;
286         }
287         int taskId = mTaskInfo.taskId;
288         mSyncQueue.runInSync((t) -> {
289             mListenerExecutor.execute(() -> {
290                 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
291             });
292         });
293     }
294 
295     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)296     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
297             SurfaceControl leash) {
298         if (isUsingShellTransitions()) {
299             mPendingInfo = taskInfo;
300             if (mTaskNotFound) {
301                 // If we were already notified by shell transit that we don't have the
302                 // the task, clean it up now.
303                 cleanUpPendingTask();
304             }
305             // Everything else handled by enter transition.
306             return;
307         }
308         mTaskInfo = taskInfo;
309         mTaskToken = taskInfo.token;
310         mTaskLeash = leash;
311 
312         if (mSurfaceCreated) {
313             // Surface is ready, so just reparent the task to this surface control
314             mTransaction.reparent(mTaskLeash, mSurfaceControl)
315                     .show(mTaskLeash)
316                     .apply();
317         } else {
318             // The surface has already been destroyed before the task has appeared,
319             // so go ahead and hide the task entirely
320             updateTaskVisibility();
321         }
322         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
323         mSyncQueue.runInSync((t) -> {
324             mTaskViewBase.onTaskAppeared(taskInfo, leash);
325         });
326 
327         if (mListener != null) {
328             final int taskId = taskInfo.taskId;
329             final ComponentName baseActivity = taskInfo.baseActivity;
330             mListenerExecutor.execute(() -> {
331                 mListener.onTaskCreated(taskId, baseActivity);
332             });
333         }
334     }
335 
336     @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)337     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
338         // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
339         // we know about -- so leave clean-up here even if shell transitions are enabled.
340         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
341 
342         final SurfaceControl taskLeash = mTaskLeash;
343         handleAndNotifyTaskRemoval(mTaskInfo);
344 
345         // Unparent the task when this surface is destroyed
346         mTransaction.reparent(taskLeash, null).apply();
347         resetTaskInfo();
348     }
349 
350     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)351     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
352         mTaskViewBase.onTaskInfoChanged(taskInfo);
353     }
354 
355     @Override
onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)356     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
357         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
358         if (mListener != null) {
359             final int taskId = taskInfo.taskId;
360             mListenerExecutor.execute(() -> {
361                 mListener.onBackPressedOnTaskRoot(taskId);
362             });
363         }
364     }
365 
366     @Override
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)367     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
368         b.setParent(findTaskSurface(taskId));
369     }
370 
371     @Override
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)372     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
373             SurfaceControl.Transaction t) {
374         t.reparent(sc, findTaskSurface(taskId));
375     }
376 
findTaskSurface(int taskId)377     private SurfaceControl findTaskSurface(int taskId) {
378         if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
379             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
380         }
381         return mTaskLeash;
382     }
383 
384     @Override
dump(@ndroidx.annotation.NonNull PrintWriter pw, String prefix)385     public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
386         final String innerPrefix = prefix + "  ";
387         final String childPrefix = innerPrefix + "  ";
388         pw.println(prefix + this);
389     }
390 
391     @Override
toString()392     public String toString() {
393         return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
394     }
395 
396     /**
397      * Should be called when the client surface is created.
398      *
399      * @param surfaceControl the {@link SurfaceControl} for the underlying surface.
400      */
surfaceCreated(SurfaceControl surfaceControl)401     public void surfaceCreated(SurfaceControl surfaceControl) {
402         mSurfaceCreated = true;
403         mIsInitialized = true;
404         mSurfaceControl = surfaceControl;
405         notifyInitialized();
406         mShellExecutor.execute(() -> {
407             if (mTaskToken == null) {
408                 // Nothing to update, task is not yet available
409                 return;
410             }
411             if (isUsingShellTransitions()) {
412                 mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
413                 return;
414             }
415             // Reparent the task when this surface is created
416             mTransaction.reparent(mTaskLeash, mSurfaceControl)
417                     .show(mTaskLeash)
418                     .apply();
419             updateTaskVisibility();
420         });
421     }
422 
423     /**
424      * Sets the window bounds to {@code boundsOnScreen}.
425      * Call when view position or size has changed. Can also be called before the animation when
426      * the final bounds are known.
427      * Do not call during the animation.
428      *
429      * @param boundsOnScreen the on screen bounds of the surface view.
430      */
setWindowBounds(Rect boundsOnScreen)431     public void setWindowBounds(Rect boundsOnScreen) {
432         if (mTaskToken == null) {
433             return;
434         }
435 
436         if (isUsingShellTransitions()) {
437             mShellExecutor.execute(() -> {
438                 // Sync Transactions can't operate simultaneously with shell transition collection.
439                 mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
440             });
441             return;
442         }
443 
444         WindowContainerTransaction wct = new WindowContainerTransaction();
445         wct.setBounds(mTaskToken, boundsOnScreen);
446         mSyncQueue.queue(wct);
447     }
448 
449     /**
450      * Call to remove the task from window manager. This task will not appear in recents.
451      */
removeTask()452     void removeTask() {
453         if (mTaskToken == null) {
454             // Call to remove task before we have one, do nothing
455             Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
456             return;
457         }
458         mShellExecutor.execute(() -> {
459             WindowContainerTransaction wct = new WindowContainerTransaction();
460             wct.removeTask(mTaskToken);
461             mTaskViewTransitions.closeTaskView(wct, this);
462         });
463     }
464 
465     /**
466      * Sets a region of the task to inset to allow for a caption bar.
467      *
468      * @param captionInsets the rect for the insets in screen coordinates.
469      */
setCaptionInsets(Rect captionInsets)470     void setCaptionInsets(Rect captionInsets) {
471         if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
472             return;
473         }
474         mCaptionInsets = captionInsets;
475         applyCaptionInsetsIfNeeded();
476     }
477 
applyCaptionInsetsIfNeeded()478     void applyCaptionInsetsIfNeeded() {
479         if (mTaskToken == null) return;
480         WindowContainerTransaction wct = new WindowContainerTransaction();
481         if (mCaptionInsets != null) {
482             wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
483                     WindowInsets.Type.captionBar(), mCaptionInsets);
484         } else {
485             wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
486                     WindowInsets.Type.captionBar());
487         }
488         mTaskOrganizer.applyTransaction(wct);
489     }
490 
491     /** Should be called when the client surface is destroyed. */
surfaceDestroyed()492     public void surfaceDestroyed() {
493         mSurfaceCreated = false;
494         mSurfaceControl = null;
495         mShellExecutor.execute(() -> {
496             if (mTaskToken == null) {
497                 // Nothing to update, task is not yet available
498                 return;
499             }
500 
501             if (isUsingShellTransitions()) {
502                 mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
503                 return;
504             }
505 
506             // Unparent the task when this surface is destroyed
507             mTransaction.reparent(mTaskLeash, null).apply();
508             updateTaskVisibility();
509         });
510     }
511 
512     /** Called when the {@link TaskViewTaskController} is initialized. */
notifyInitialized()513     protected void notifyInitialized() {
514         if (mListener != null && !mNotifiedForInitialized) {
515             mNotifiedForInitialized = true;
516             mListenerExecutor.execute(() -> {
517                 mListener.onInitialized();
518             });
519         }
520     }
521 
522     /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo)523     private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
524         if (taskInfo != null) {
525             if (mListener != null) {
526                 final int taskId = taskInfo.taskId;
527                 mListenerExecutor.execute(() -> {
528                     mListener.onTaskRemovalStarted(taskId);
529                 });
530             }
531             mTaskViewBase.onTaskVanished(taskInfo);
532             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false);
533         }
534     }
535 
536     /** Returns the task info for the task in the TaskView. */
537     @Nullable
getTaskInfo()538     public ActivityManager.RunningTaskInfo getTaskInfo() {
539         return mTaskInfo;
540     }
541 
542     /**
543      * Indicates that the task was not found in the start animation for the transition.
544      * In this case we should clean up the task if we have the pending info. If we don't
545      * have the pending info, we'll do it when we receive it in
546      * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}.
547      */
setTaskNotFound()548     void setTaskNotFound() {
549         mTaskNotFound = true;
550         if (mPendingInfo != null) {
551             cleanUpPendingTask();
552         }
553     }
554 
555     /**
556      * Called when a task failed to open and we need to clean up task view /
557      * notify users of task view.
558      */
cleanUpPendingTask()559     void cleanUpPendingTask() {
560         if (mPendingInfo != null) {
561             final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo;
562             handleAndNotifyTaskRemoval(pendingInfo);
563 
564             // Make sure the task is removed
565             WindowContainerTransaction wct = new WindowContainerTransaction();
566             wct.removeTask(pendingInfo.token);
567             mTaskViewTransitions.closeTaskView(wct, this);
568         }
569         resetTaskInfo();
570     }
571 
prepareHideAnimation(@onNull SurfaceControl.Transaction finishTransaction)572     void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
573         if (mTaskToken == null) {
574             // Nothing to update, task is not yet available
575             return;
576         }
577 
578         finishTransaction.reparent(mTaskLeash, null);
579 
580         if (mListener != null) {
581             final int taskId = mTaskInfo.taskId;
582             mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
583         }
584     }
585 
586     /**
587      * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
588      * is used instead.
589      */
prepareCloseAnimation()590     void prepareCloseAnimation() {
591         handleAndNotifyTaskRemoval(mTaskInfo);
592         resetTaskInfo();
593     }
594 
prepareOpenAnimation(final boolean newTask, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)595     void prepareOpenAnimation(final boolean newTask,
596             @NonNull SurfaceControl.Transaction startTransaction,
597             @NonNull SurfaceControl.Transaction finishTransaction,
598             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
599             WindowContainerTransaction wct) {
600         mPendingInfo = null;
601         mTaskInfo = taskInfo;
602         mTaskToken = mTaskInfo.token;
603         mTaskLeash = leash;
604         if (mSurfaceCreated) {
605             // Surface is ready, so just reparent the task to this surface control
606             startTransaction.reparent(mTaskLeash, mSurfaceControl)
607                     .show(mTaskLeash);
608             // Also reparent on finishTransaction since the finishTransaction will reparent back
609             // to its "original" parent by default.
610             Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen();
611             finishTransaction.reparent(mTaskLeash, mSurfaceControl)
612                     .setPosition(mTaskLeash, 0, 0)
613                     // TODO: maybe once b/280900002 is fixed this will be unnecessary
614                     .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height());
615             mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
616             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
617             wct.setBounds(mTaskToken, boundsOnScreen);
618             applyCaptionInsetsIfNeeded();
619         } else {
620             // The surface has already been destroyed before the task has appeared,
621             // so go ahead and hide the task entirely
622             wct.setHidden(mTaskToken, true /* hidden */);
623             mTaskViewTransitions.updateVisibilityState(this, false /* visible */);
624             // listener callback is below
625         }
626         if (newTask) {
627             mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
628         }
629 
630         if (mTaskInfo.taskDescription != null) {
631             int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
632             mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
633         }
634 
635         if (mListener != null) {
636             final int taskId = mTaskInfo.taskId;
637             final ComponentName baseActivity = mTaskInfo.baseActivity;
638 
639             mListenerExecutor.execute(() -> {
640                 if (newTask) {
641                     mListener.onTaskCreated(taskId, baseActivity);
642                 }
643                 // Even if newTask, send a visibilityChange if the surface was destroyed.
644                 if (!newTask || !mSurfaceCreated) {
645                     mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
646                 }
647             });
648         }
649     }
650 }
651