• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.graphics.Rect;
25 import android.gui.TrustedOverlay;
26 import android.os.Binder;
27 import android.util.CloseGuard;
28 import android.view.SurfaceControl;
29 import android.view.WindowInsets;
30 import android.window.WindowContainerToken;
31 import android.window.WindowContainerTransaction;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.wm.shell.ShellTaskOrganizer;
35 import com.android.wm.shell.common.SyncTransactionQueue;
36 
37 import java.io.PrintWriter;
38 import java.util.concurrent.Executor;
39 
40 /**
41  * This class represents the visible aspect of a task in a {@link TaskView}. All the {@link
42  * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
43  *
44  * The reverse communication is done via the {@link TaskViewBase} interface.
45  */
46 public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
47 
48     private static final String TAG = TaskViewTaskController.class.getSimpleName();
49 
50     private final CloseGuard mGuard = new CloseGuard();
51     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
52     /** Used to inset the activity content to allow space for a caption bar. */
53     private final Binder mCaptionInsetsOwner = new Binder();
54     private final ShellTaskOrganizer mTaskOrganizer;
55     private final Executor mShellExecutor;
56     private final SyncTransactionQueue mSyncQueue;
57     private final TaskViewController mTaskViewController;
58     private final Context mContext;
59 
60     /**
61      * There could be a situation where we have task info and receive
62      * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the
63      * activity might fail to open, and in this case we need to clean up the task view / notify
64      * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared
65      * in this situation to allow us to notify listeners correctly if the task failed to open.
66      */
67     private ActivityManager.RunningTaskInfo mPendingInfo;
68     private TaskViewBase mTaskViewBase;
69     protected ActivityManager.RunningTaskInfo mTaskInfo;
70     private WindowContainerToken mTaskToken;
71     private SurfaceControl mTaskLeash;
72     /* Indicates that the task we attempted to launch in the task view failed to launch. */
73     private boolean mTaskNotFound;
74     private boolean mSurfaceCreated;
75     private SurfaceControl mSurfaceControl;
76     private boolean mIsInitialized;
77     private boolean mNotifiedForInitialized;
78     private boolean mHideTaskWithSurface = true;
79     private TaskView.Listener mListener;
80     private Executor mListenerExecutor;
81     private Rect mCaptionInsets;
82 
TaskViewTaskController(Context context, ShellTaskOrganizer organizer, TaskViewController taskViewController, SyncTransactionQueue syncQueue)83     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
84             TaskViewController taskViewController, SyncTransactionQueue syncQueue) {
85         mContext = context;
86         mTaskOrganizer = organizer;
87         mShellExecutor = organizer.getExecutor();
88         mSyncQueue = syncQueue;
89         mTaskViewController = taskViewController;
90         mShellExecutor.execute(() -> {
91             if (mTaskViewController != null) {
92                 mTaskViewController.registerTaskView(this);
93             }
94         });
95         mGuard.open("release");
96     }
97 
98     /**
99      * Specifies if the task should be hidden when the surface is destroyed.
100      * <p>This is {@code true} by default.
101      *
102      * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
103      *                            surface is destroyed, {@code true} otherwise.
104      */
setHideTaskWithSurface(boolean hideTaskWithSurface)105     public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
106         // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
107         // are moved to a window in SystemUI in auto.
108         mHideTaskWithSurface = hideTaskWithSurface;
109     }
110 
getSurfaceControl()111     SurfaceControl getSurfaceControl() {
112         return mSurfaceControl;
113     }
114 
getContext()115     Context getContext() {
116         return mContext;
117     }
118 
119     /**
120      * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
121      * task related changes and getting the current bounds.
122      */
setTaskViewBase(TaskViewBase taskViewBase)123     public void setTaskViewBase(TaskViewBase taskViewBase) {
124         mTaskViewBase = taskViewBase;
125     }
126 
127     /**
128      * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
129      */
isInitialized()130     public boolean isInitialized() {
131         return mIsInitialized;
132     }
133 
getTaskToken()134     WindowContainerToken getTaskToken() {
135         return mTaskToken;
136     }
137 
setResizeBgColor(SurfaceControl.Transaction t, int bgColor)138     void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
139         mTaskViewBase.setResizeBgColor(t, bgColor);
140     }
141 
142     /**
143      * Only one listener may be set on the view, throws an exception otherwise.
144      */
setListener(@onNull Executor executor, TaskView.Listener listener)145     void setListener(@NonNull Executor executor, TaskView.Listener listener) {
146         if (mListener != null) {
147             throw new IllegalStateException(
148                     "Trying to set a listener when one has already been set");
149         }
150         mListener = listener;
151         mListenerExecutor = executor;
152     }
153 
154     /**
155      * Release this container if it is initialized.
156      */
release()157     public void release() {
158         performRelease();
159     }
160 
161     @Override
finalize()162     protected void finalize() throws Throwable {
163         try {
164             if (mGuard != null) {
165                 mGuard.warnIfOpen();
166                 performRelease();
167             }
168         } finally {
169             super.finalize();
170         }
171     }
172 
performRelease()173     private void performRelease() {
174         mShellExecutor.execute(() -> {
175             if (mTaskViewController != null) {
176                 mTaskViewController.unregisterTaskView(this);
177             }
178             mTaskOrganizer.removeListener(this);
179             resetTaskInfo();
180         });
181         mGuard.close();
182         mIsInitialized = false;
183         notifyReleased();
184     }
185 
186     /** Called when the {@link TaskViewTaskController} has been released. */
notifyReleased()187     protected void notifyReleased() {
188         if (mListener != null && mNotifiedForInitialized) {
189             mListenerExecutor.execute(() -> {
190                 mListener.onReleased();
191             });
192             mNotifiedForInitialized = false;
193         }
194     }
195 
resetTaskInfo()196     private void resetTaskInfo() {
197         mTaskInfo = null;
198         mTaskToken = null;
199         mTaskLeash = null;
200         mPendingInfo = null;
201         mTaskNotFound = false;
202     }
203 
204     /** This method shouldn't be called when shell transitions are enabled. */
updateTaskVisibility()205     private void updateTaskVisibility() {
206         boolean visible = mSurfaceCreated;
207         if (!visible && !mHideTaskWithSurface) {
208             return;
209         }
210         WindowContainerTransaction wct = new WindowContainerTransaction();
211         wct.setHidden(mTaskToken, !visible /* hidden */);
212         if (!visible) {
213             wct.reorder(mTaskToken, false /* onTop */);
214         }
215         mSyncQueue.queue(wct);
216         if (mListener == null) {
217             return;
218         }
219         int taskId = mTaskInfo.taskId;
220         mSyncQueue.runInSync((t) -> {
221             mListenerExecutor.execute(() -> {
222                 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
223             });
224         });
225     }
226 
227     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)228     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
229             SurfaceControl leash) {
230         if (mTaskViewController.isUsingShellTransitions()) {
231             mPendingInfo = taskInfo;
232             if (mTaskNotFound) {
233                 // If we were already notified by shell transit that we don't have the
234                 // the task, clean it up now.
235                 cleanUpPendingTask();
236             }
237             // Everything else handled by enter transition.
238             return;
239         }
240         mTaskInfo = taskInfo;
241         mTaskToken = taskInfo.token;
242         mTaskLeash = leash;
243 
244         if (mSurfaceCreated) {
245             // Surface is ready, so just reparent the task to this surface control
246             mTransaction.reparent(mTaskLeash, mSurfaceControl)
247                     .show(mTaskLeash)
248                     .apply();
249         } else {
250             // The surface has already been destroyed before the task has appeared,
251             // so go ahead and hide the task entirely
252             updateTaskVisibility();
253         }
254         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
255         mSyncQueue.runInSync((t) -> {
256             mTaskViewBase.onTaskAppeared(taskInfo, leash);
257         });
258 
259         if (mListener != null) {
260             final int taskId = taskInfo.taskId;
261             final ComponentName baseActivity = taskInfo.baseActivity;
262             mListenerExecutor.execute(() -> {
263                 mListener.onTaskCreated(taskId, baseActivity);
264             });
265         }
266     }
267 
268     @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)269     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
270         // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
271         // we know about -- so leave clean-up here even if shell transitions are enabled.
272         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
273 
274         final SurfaceControl taskLeash = mTaskLeash;
275         handleAndNotifyTaskRemoval(mTaskInfo);
276 
277         mTransaction.reparent(taskLeash, null).apply();
278         resetTaskInfo();
279     }
280 
281     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)282     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
283         mTaskViewBase.onTaskInfoChanged(taskInfo);
284     }
285 
286     @Override
onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)287     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
288         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
289         if (mListener != null) {
290             final int taskId = taskInfo.taskId;
291             mListenerExecutor.execute(() -> {
292                 mListener.onBackPressedOnTaskRoot(taskId);
293             });
294         }
295     }
296 
297     @Override
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)298     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
299         b.setParent(findTaskSurface(taskId));
300     }
301 
302     @Override
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)303     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
304             SurfaceControl.Transaction t) {
305         t.reparent(sc, findTaskSurface(taskId));
306     }
307 
findTaskSurface(int taskId)308     private SurfaceControl findTaskSurface(int taskId) {
309         if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) {
310             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
311         }
312         return mTaskLeash;
313     }
314 
315     @Override
dump(@ndroidx.annotation.NonNull PrintWriter pw, String prefix)316     public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
317         final String innerPrefix = prefix + "  ";
318         final String childPrefix = innerPrefix + "  ";
319         pw.println(prefix + this);
320     }
321 
322     @Override
toString()323     public String toString() {
324         return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null");
325     }
326 
327     /**
328      * Should be called when the client surface is created.
329      *
330      * @param surfaceControl the {@link SurfaceControl} for the underlying surface.
331      */
surfaceCreated(SurfaceControl surfaceControl)332     public void surfaceCreated(SurfaceControl surfaceControl) {
333         mSurfaceCreated = true;
334         mIsInitialized = true;
335         mSurfaceControl = surfaceControl;
336         // SurfaceControl is expected to be null only in the case of unit tests. Guard against it
337         // to avoid runtime exception in SurfaceControl.Transaction.
338         if (surfaceControl != null) {
339             // TaskView is meant to contain app activities which shouldn't have trusted overlays
340             // flag set even when itself reparented in a window which is trusted.
341             mTransaction.setTrustedOverlay(surfaceControl, TrustedOverlay.DISABLED)
342                     .apply();
343         }
344         notifyInitialized();
345         mShellExecutor.execute(() -> {
346             if (mTaskToken == null) {
347                 // Nothing to update, task is not yet available
348                 return;
349             }
350             if (mTaskViewController.isUsingShellTransitions()) {
351                 mTaskViewController.setTaskViewVisible(this, true /* visible */);
352                 return;
353             }
354             // Reparent the task when this surface is created
355             mTransaction.reparent(mTaskLeash, mSurfaceControl)
356                     .show(mTaskLeash)
357                     .apply();
358             updateTaskVisibility();
359         });
360     }
361 
362     /**
363      * Sets a region of the task to inset to allow for a caption bar.
364      *
365      * @param captionInsets the rect for the insets in screen coordinates.
366      */
setCaptionInsets(Rect captionInsets)367     void setCaptionInsets(Rect captionInsets) {
368         if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
369             return;
370         }
371         mCaptionInsets = captionInsets;
372         applyCaptionInsetsIfNeeded();
373     }
374 
applyCaptionInsetsIfNeeded()375     void applyCaptionInsetsIfNeeded() {
376         if (mTaskToken == null) return;
377         WindowContainerTransaction wct = new WindowContainerTransaction();
378         if (mCaptionInsets != null) {
379             wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
380                     WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */,
381                     0 /* flags */);
382         } else {
383             wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
384                     WindowInsets.Type.captionBar());
385         }
386         mTaskOrganizer.applyTransaction(wct);
387     }
388 
389     /** Should be called when the client surface is destroyed. */
surfaceDestroyed()390     public void surfaceDestroyed() {
391         mSurfaceCreated = false;
392         mSurfaceControl = null;
393         mShellExecutor.execute(() -> {
394             if (mTaskToken == null) {
395                 // Nothing to update, task is not yet available
396                 return;
397             }
398 
399             if (mTaskViewController.isUsingShellTransitions()) {
400                 mTaskViewController.setTaskViewVisible(this, false /* visible */);
401                 return;
402             }
403 
404             // Unparent the task when this surface is destroyed
405             mTransaction.reparent(mTaskLeash, null).apply();
406             updateTaskVisibility();
407         });
408     }
409 
410     /** Called when the {@link TaskViewTaskController} is initialized. */
notifyInitialized()411     protected void notifyInitialized() {
412         if (mListener != null && !mNotifiedForInitialized) {
413             mNotifiedForInitialized = true;
414             mListenerExecutor.execute(() -> {
415                 mListener.onInitialized();
416             });
417         }
418     }
419 
420     /** Notifies listeners of a task being removed. */
notifyTaskRemovalStarted(@onNull ActivityManager.RunningTaskInfo taskInfo)421     public void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
422         if (mListener == null) return;
423         final int taskId = taskInfo.taskId;
424         mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId));
425     }
426 
427     /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo)428     private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
429         if (taskInfo != null) {
430             notifyTaskRemovalStarted(taskInfo);
431             mTaskViewBase.onTaskVanished(taskInfo);
432         }
433     }
434 
435     /** Returns the task info for the task in the TaskView. */
436     @Nullable
getTaskInfo()437     public ActivityManager.RunningTaskInfo getTaskInfo() {
438         return mTaskInfo;
439     }
440 
441     @VisibleForTesting
getPendingInfo()442     ActivityManager.RunningTaskInfo getPendingInfo() {
443         return mPendingInfo;
444     }
445 
446     /**
447      * Indicates that the task was not found in the start animation for the transition.
448      * In this case we should clean up the task if we have the pending info. If we don't
449      * have the pending info, we'll do it when we receive it in
450      * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}.
451      */
setTaskNotFound()452     public void setTaskNotFound() {
453         mTaskNotFound = true;
454         if (mPendingInfo != null) {
455             cleanUpPendingTask();
456         }
457     }
458 
459     /**
460      * Called when a task failed to open and we need to clean up task view /
461      * notify users of task view.
462      */
cleanUpPendingTask()463     void cleanUpPendingTask() {
464         if (mPendingInfo != null) {
465             final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo;
466             handleAndNotifyTaskRemoval(pendingInfo);
467 
468             // Make sure the task is removed
469             mTaskViewController.removeTaskView(this, pendingInfo.token);
470         }
471         resetTaskInfo();
472     }
473 
prepareHideAnimation(@onNull SurfaceControl.Transaction finishTransaction)474     void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
475         if (mTaskToken == null) {
476             // Nothing to update, task is not yet available
477             return;
478         }
479 
480         finishTransaction.reparent(mTaskLeash, null);
481 
482         if (mListener != null) {
483             final int taskId = mTaskInfo.taskId;
484             mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
485         }
486     }
487 
488     /**
489      * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
490      * is used instead.
491      */
prepareCloseAnimation()492     void prepareCloseAnimation() {
493         handleAndNotifyTaskRemoval(mTaskInfo);
494         resetTaskInfo();
495     }
496 
497     /**
498      * Prepare this taskview to open {@param taskInfo}.
499      * @return The bounds of the task or {@code null} on failure (surface is destroyed)
500      */
prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)501     Rect prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
502         mPendingInfo = null;
503         mTaskInfo = taskInfo;
504         mTaskToken = mTaskInfo.token;
505         mTaskLeash = leash;
506         if (!mSurfaceCreated) {
507             return null;
508         }
509         return mTaskViewBase.getCurrentBoundsOnScreen();
510     }
511 
512     /** Notify that the associated task has appeared. This will call appropriate listeners. */
notifyAppeared(final boolean newTask)513     void notifyAppeared(final boolean newTask) {
514         mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash);
515         if (mListener != null) {
516             final int taskId = mTaskInfo.taskId;
517             final ComponentName baseActivity = mTaskInfo.baseActivity;
518 
519             mListenerExecutor.execute(() -> {
520                 if (newTask) {
521                     mListener.onTaskCreated(taskId, baseActivity);
522                 }
523                 // Even if newTask, send a visibilityChange if the surface was destroyed.
524                 if (!newTask || !mSurfaceCreated) {
525                     mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
526                 }
527             });
528         }
529     }
530 }
531