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;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
24 
25 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
26 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
27 
28 import android.annotation.IntDef;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.app.ActivityManager.RunningTaskInfo;
32 import android.app.TaskInfo;
33 import android.app.WindowConfiguration;
34 import android.content.LocusId;
35 import android.content.pm.ActivityInfo;
36 import android.graphics.Rect;
37 import android.os.Binder;
38 import android.os.IBinder;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Log;
42 import android.util.SparseArray;
43 import android.view.SurfaceControl;
44 import android.window.ITaskOrganizerController;
45 import android.window.ScreenCapture;
46 import android.window.StartingWindowInfo;
47 import android.window.StartingWindowRemovalInfo;
48 import android.window.TaskAppearedInfo;
49 import android.window.TaskOrganizer;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.protolog.common.ProtoLog;
53 import com.android.internal.util.FrameworkStatsLog;
54 import com.android.wm.shell.common.ScreenshotUtils;
55 import com.android.wm.shell.common.ShellExecutor;
56 import com.android.wm.shell.compatui.CompatUIController;
57 import com.android.wm.shell.recents.RecentTasksController;
58 import com.android.wm.shell.startingsurface.StartingWindowController;
59 import com.android.wm.shell.sysui.ShellCommandHandler;
60 import com.android.wm.shell.sysui.ShellInit;
61 import com.android.wm.shell.unfold.UnfoldAnimationController;
62 
63 import java.io.PrintWriter;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.Optional;
69 import java.util.function.Consumer;
70 
71 /**
72  * Unified task organizer for all components in the shell.
73  * TODO(b/167582004): may consider consolidating this class and TaskOrganizer
74  */
75 public class ShellTaskOrganizer extends TaskOrganizer implements
76         CompatUIController.CompatUICallback {
77     private static final String TAG = "ShellTaskOrganizer";
78 
79     // Intentionally using negative numbers here so the positive numbers can be used
80     // for task id specific listeners that will be added later.
81     public static final int TASK_LISTENER_TYPE_UNDEFINED = -1;
82     public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
83     public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
84     public static final int TASK_LISTENER_TYPE_PIP = -4;
85     public static final int TASK_LISTENER_TYPE_FREEFORM = -5;
86 
87     @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
88             TASK_LISTENER_TYPE_UNDEFINED,
89             TASK_LISTENER_TYPE_FULLSCREEN,
90             TASK_LISTENER_TYPE_MULTI_WINDOW,
91             TASK_LISTENER_TYPE_PIP,
92             TASK_LISTENER_TYPE_FREEFORM,
93     })
94     public @interface TaskListenerType {}
95 
96     /**
97      * Callbacks for when the tasks change in the system.
98      */
99     public interface TaskListener {
onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)100         default void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {}
onTaskInfoChanged(RunningTaskInfo taskInfo)101         default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
onTaskVanished(RunningTaskInfo taskInfo)102         default void onTaskVanished(RunningTaskInfo taskInfo) {}
onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)103         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
104         /** Whether this task listener supports compat UI. */
supportCompatUI()105         default boolean supportCompatUI() {
106             // All TaskListeners should support compat UI except PIP and StageCoordinator.
107             return true;
108         }
109         /** Attaches a child window surface to the task surface. */
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)110         default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
111             throw new IllegalStateException(
112                     "This task listener doesn't support child surface attachment.");
113         }
114         /** Reparents a child window surface to the task surface. */
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)115         default void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
116                 SurfaceControl.Transaction t) {
117             throw new IllegalStateException(
118                     "This task listener doesn't support child surface reparent.");
119         }
dump(@onNull PrintWriter pw, String prefix)120         default void dump(@NonNull PrintWriter pw, String prefix) {};
121     }
122 
123     /**
124      * Callbacks for events on a task with a locus id.
125      */
126     public interface LocusIdListener {
127         /**
128          * Notifies when a task with a locusId becomes visible, when a visible task's locusId
129          * changes, or if a previously visible task with a locusId becomes invisible.
130          */
onVisibilityChanged(int taskId, LocusId locus, boolean visible)131         void onVisibilityChanged(int taskId, LocusId locus, boolean visible);
132     }
133 
134     /**
135      * Callbacks for events in which the focus has changed.
136      */
137     public interface FocusListener {
138         /**
139          * Notifies when the task which is focused has changed.
140          */
onFocusTaskChanged(RunningTaskInfo taskInfo)141         void onFocusTaskChanged(RunningTaskInfo taskInfo);
142     }
143 
144     /**
145      * Keys map from either a task id or {@link TaskListenerType}.
146      * @see #addListenerForTaskId
147      * @see #addListenerForType
148      */
149     private final SparseArray<TaskListener> mTaskListeners = new SparseArray<>();
150 
151     // Keeps track of all the tasks reported to this organizer (changes in windowing mode will
152     // require us to report to both old and new listeners)
153     private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>();
154 
155     /** @see #setPendingLaunchCookieListener */
156     private final ArrayMap<IBinder, TaskListener> mLaunchCookieToListener = new ArrayMap<>();
157 
158     // Keeps track of taskId's with visible locusIds. Used to notify any {@link LocusIdListener}s
159     // that might be set.
160     private final SparseArray<LocusId> mVisibleTasksWithLocusId = new SparseArray<>();
161 
162     /** @see #addLocusIdListener */
163     private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
164 
165     private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>();
166 
167     private final Object mLock = new Object();
168     private StartingWindowController mStartingWindow;
169 
170     /**
171      * In charge of showing compat UI. Can be {@code null} if the device doesn't support size
172      * compat or if this isn't the main {@link ShellTaskOrganizer}.
173      *
174      * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController},
175      * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be
176      * initialized with a {@code null} {@link CompatUIController}.
177      */
178     @Nullable
179     private final CompatUIController mCompatUI;
180 
181     @NonNull
182     private final ShellCommandHandler mShellCommandHandler;
183 
184     @Nullable
185     private final Optional<RecentTasksController> mRecentTasks;
186 
187     @Nullable
188     private final UnfoldAnimationController mUnfoldAnimationController;
189 
190     @Nullable
191     private RunningTaskInfo mLastFocusedTaskInfo;
192 
ShellTaskOrganizer(ShellExecutor mainExecutor)193     public ShellTaskOrganizer(ShellExecutor mainExecutor) {
194         this(null /* shellInit */, null /* shellCommandHandler */,
195                 null /* taskOrganizerController */, null /* compatUI */,
196                 Optional.empty() /* unfoldAnimationController */,
197                 Optional.empty() /* recentTasksController */,
198                 mainExecutor);
199     }
200 
ShellTaskOrganizer(ShellInit shellInit, ShellCommandHandler shellCommandHandler, @Nullable CompatUIController compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor)201     public ShellTaskOrganizer(ShellInit shellInit,
202             ShellCommandHandler shellCommandHandler,
203             @Nullable CompatUIController compatUI,
204             Optional<UnfoldAnimationController> unfoldAnimationController,
205             Optional<RecentTasksController> recentTasks,
206             ShellExecutor mainExecutor) {
207         this(shellInit, shellCommandHandler, null /* taskOrganizerController */, compatUI,
208                 unfoldAnimationController, recentTasks, mainExecutor);
209     }
210 
211     @VisibleForTesting
ShellTaskOrganizer(ShellInit shellInit, ShellCommandHandler shellCommandHandler, ITaskOrganizerController taskOrganizerController, @Nullable CompatUIController compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor)212     protected ShellTaskOrganizer(ShellInit shellInit,
213             ShellCommandHandler shellCommandHandler,
214             ITaskOrganizerController taskOrganizerController,
215             @Nullable CompatUIController compatUI,
216             Optional<UnfoldAnimationController> unfoldAnimationController,
217             Optional<RecentTasksController> recentTasks,
218             ShellExecutor mainExecutor) {
219         super(taskOrganizerController, mainExecutor);
220         mShellCommandHandler = shellCommandHandler;
221         mCompatUI = compatUI;
222         mRecentTasks = recentTasks;
223         mUnfoldAnimationController = unfoldAnimationController.orElse(null);
224         if (shellInit != null) {
225             shellInit.addInitCallback(this::onInit, this);
226         }
227     }
228 
onInit()229     private void onInit() {
230         mShellCommandHandler.addDumpCallback(this::dump, this);
231         if (mCompatUI != null) {
232             mCompatUI.setCompatUICallback(this);
233         }
234         registerOrganizer();
235     }
236 
237     @Override
registerOrganizer()238     public List<TaskAppearedInfo> registerOrganizer() {
239         synchronized (mLock) {
240             ProtoLog.v(WM_SHELL_TASK_ORG, "Registering organizer");
241             final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
242             for (int i = 0; i < taskInfos.size(); i++) {
243                 final TaskAppearedInfo info = taskInfos.get(i);
244                 ProtoLog.v(WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s",
245                         info.getTaskInfo().taskId, info.getTaskInfo().baseIntent);
246                 onTaskAppeared(info);
247             }
248             return taskInfos;
249         }
250     }
251 
252     @Override
unregisterOrganizer()253     public void unregisterOrganizer() {
254         super.unregisterOrganizer();
255         if (mStartingWindow != null) {
256             mStartingWindow.clearAllWindows();
257         }
258     }
259 
260     /**
261      * Creates a persistent root task in WM for a particular windowing-mode.
262      * @param displayId The display to create the root task on.
263      * @param windowingMode Windowing mode to put the root task in.
264      * @param listener The listener to get the created task callback.
265      */
createRootTask(int displayId, int windowingMode, TaskListener listener)266     public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
267         createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */);
268     }
269 
270     /**
271      * Creates a persistent root task in WM for a particular windowing-mode.
272      * @param displayId The display to create the root task on.
273      * @param windowingMode Windowing mode to put the root task in.
274      * @param listener The listener to get the created task callback.
275      * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
276      */
createRootTask(int displayId, int windowingMode, TaskListener listener, boolean removeWithTaskOrganizer)277     public void createRootTask(int displayId, int windowingMode, TaskListener listener,
278             boolean removeWithTaskOrganizer) {
279         ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" ,
280                 displayId, windowingMode, listener.toString());
281         final IBinder cookie = new Binder();
282         setPendingLaunchCookieListener(cookie, listener);
283         super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer);
284     }
285 
286     /**
287      * @hide
288      */
initStartingWindow(StartingWindowController startingWindow)289     public void initStartingWindow(StartingWindowController startingWindow) {
290         mStartingWindow = startingWindow;
291     }
292 
293     /**
294      * Adds a listener for a specific task id.
295      */
addListenerForTaskId(TaskListener listener, int taskId)296     public void addListenerForTaskId(TaskListener listener, int taskId) {
297         synchronized (mLock) {
298             ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForTaskId taskId=%s", taskId);
299             if (mTaskListeners.get(taskId) != null) {
300                 throw new IllegalArgumentException(
301                         "Listener for taskId=" + taskId + " already exists");
302             }
303 
304             final TaskAppearedInfo info = mTasks.get(taskId);
305             if (info == null) {
306                 throw new IllegalArgumentException("addListenerForTaskId unknown taskId=" + taskId);
307             }
308 
309             final TaskListener oldListener = getTaskListener(info.getTaskInfo());
310             mTaskListeners.put(taskId, listener);
311             updateTaskListenerIfNeeded(info.getTaskInfo(), info.getLeash(), oldListener, listener);
312         }
313     }
314 
315     /**
316      * Adds a listener for tasks with given types.
317      */
addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes)318     public void addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes) {
319         synchronized (mLock) {
320             ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForType types=%s listener=%s",
321                     Arrays.toString(listenerTypes), listener);
322             for (int listenerType : listenerTypes) {
323                 if (mTaskListeners.get(listenerType) != null) {
324                     throw new IllegalArgumentException("Listener for listenerType=" + listenerType
325                             + " already exists");
326                 }
327                 mTaskListeners.put(listenerType, listener);
328             }
329 
330             // Notify the listener of all existing tasks with the given type.
331             for (int i = mTasks.size() - 1; i >= 0; --i) {
332                 final TaskAppearedInfo data = mTasks.valueAt(i);
333                 final TaskListener taskListener = getTaskListener(data.getTaskInfo());
334                 if (taskListener != listener) continue;
335                 listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
336             }
337         }
338     }
339 
340     /**
341      * Removes a registered listener.
342      */
removeListener(TaskListener listener)343     public void removeListener(TaskListener listener) {
344         synchronized (mLock) {
345             ProtoLog.v(WM_SHELL_TASK_ORG, "Remove listener=%s", listener);
346             final int index = mTaskListeners.indexOfValue(listener);
347             if (index == -1) {
348                 Log.w(TAG, "No registered listener found");
349                 return;
350             }
351 
352             // Collect tasks associated with the listener we are about to remove.
353             final ArrayList<TaskAppearedInfo> tasks = new ArrayList<>();
354             for (int i = mTasks.size() - 1; i >= 0; --i) {
355                 final TaskAppearedInfo data = mTasks.valueAt(i);
356                 final TaskListener taskListener = getTaskListener(data.getTaskInfo());
357                 if (taskListener != listener) continue;
358                 tasks.add(data);
359             }
360 
361             // Remove listener, there can be the multiple occurrences, so search the whole list.
362             for (int i = mTaskListeners.size() - 1; i >= 0; --i) {
363                 if (mTaskListeners.valueAt(i) == listener) {
364                     mTaskListeners.removeAt(i);
365                 }
366             }
367 
368             // Associate tasks with new listeners if needed.
369             for (int i = tasks.size() - 1; i >= 0; --i) {
370                 final TaskAppearedInfo data = tasks.get(i);
371                 updateTaskListenerIfNeeded(data.getTaskInfo(), data.getLeash(),
372                         null /* oldListener already removed*/, getTaskListener(data.getTaskInfo()));
373             }
374         }
375     }
376 
377     /**
378      * Associated a listener to a pending launch cookie so we can route the task later once it
379      * appears.
380      */
setPendingLaunchCookieListener(IBinder cookie, TaskListener listener)381     public void setPendingLaunchCookieListener(IBinder cookie, TaskListener listener) {
382         synchronized (mLock) {
383             mLaunchCookieToListener.put(cookie, listener);
384         }
385     }
386 
387     /**
388      * Adds a listener to be notified for {@link LocusId} visibility changes.
389      */
addLocusIdListener(LocusIdListener listener)390     public void addLocusIdListener(LocusIdListener listener) {
391         synchronized (mLock) {
392             mLocusIdListeners.add(listener);
393             for (int i = 0; i < mVisibleTasksWithLocusId.size(); i++) {
394                 listener.onVisibilityChanged(mVisibleTasksWithLocusId.keyAt(i),
395                         mVisibleTasksWithLocusId.valueAt(i), true /* visible */);
396             }
397         }
398     }
399 
400     /**
401      * Removes listener.
402      */
removeLocusIdListener(LocusIdListener listener)403     public void removeLocusIdListener(LocusIdListener listener) {
404         synchronized (mLock) {
405             mLocusIdListeners.remove(listener);
406         }
407     }
408 
409     /**
410      * Adds a listener to be notified for task focus changes.
411      */
addFocusListener(FocusListener listener)412     public void addFocusListener(FocusListener listener) {
413         synchronized (mLock) {
414             mFocusListeners.add(listener);
415             if (mLastFocusedTaskInfo != null) {
416                 listener.onFocusTaskChanged(mLastFocusedTaskInfo);
417             }
418         }
419     }
420 
421     /**
422      * Removes listener.
423      */
removeFocusListener(FocusListener listener)424     public void removeFocusListener(FocusListener listener) {
425         synchronized (mLock) {
426             mFocusListeners.remove(listener);
427         }
428     }
429 
430     @Override
addStartingWindow(StartingWindowInfo info)431     public void addStartingWindow(StartingWindowInfo info) {
432         if (mStartingWindow != null) {
433             mStartingWindow.addStartingWindow(info);
434         }
435     }
436 
437     @Override
removeStartingWindow(StartingWindowRemovalInfo removalInfo)438     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
439         if (mStartingWindow != null) {
440             mStartingWindow.removeStartingWindow(removalInfo);
441         }
442     }
443 
444     @Override
copySplashScreenView(int taskId)445     public void copySplashScreenView(int taskId) {
446         if (mStartingWindow != null) {
447             mStartingWindow.copySplashScreenView(taskId);
448         }
449     }
450 
451     @Override
onAppSplashScreenViewRemoved(int taskId)452     public void onAppSplashScreenViewRemoved(int taskId) {
453         if (mStartingWindow != null) {
454             mStartingWindow.onAppSplashScreenViewRemoved(taskId);
455         }
456     }
457 
458     @Override
onImeDrawnOnTask(int taskId)459     public void onImeDrawnOnTask(int taskId) {
460         if (mStartingWindow != null) {
461             mStartingWindow.onImeDrawnOnTask(taskId);
462         }
463     }
464 
465     @Override
onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)466     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
467         if (leash != null) {
468             leash.setUnreleasedWarningCallSite("ShellTaskOrganizer.onTaskAppeared");
469         }
470         synchronized (mLock) {
471             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
472         }
473     }
474 
onTaskAppeared(TaskAppearedInfo info)475     private void onTaskAppeared(TaskAppearedInfo info) {
476         final int taskId = info.getTaskInfo().taskId;
477         mTasks.put(taskId, info);
478         final TaskListener listener =
479                 getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/);
480         ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener);
481         if (listener != null) {
482             listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
483         }
484         if (mUnfoldAnimationController != null) {
485             mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash());
486         }
487         notifyLocusVisibilityIfNeeded(info.getTaskInfo());
488         notifyCompatUI(info.getTaskInfo(), listener);
489         mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
490     }
491 
492     /**
493      * Take a screenshot of a task.
494      */
screenshotTask(RunningTaskInfo taskInfo, Rect crop, Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer)495     public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
496             Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
497         final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
498         if (info == null) {
499             return;
500         }
501         ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
502     }
503 
504 
505     @Override
onTaskInfoChanged(RunningTaskInfo taskInfo)506     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
507         synchronized (mLock) {
508             ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
509 
510             if (mUnfoldAnimationController != null) {
511                 mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
512             }
513 
514             final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
515             final TaskListener oldListener = getTaskListener(data.getTaskInfo());
516             final TaskListener newListener = getTaskListener(taskInfo);
517             mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
518             final boolean updated = updateTaskListenerIfNeeded(
519                     taskInfo, data.getLeash(), oldListener, newListener);
520             if (!updated && newListener != null) {
521                 newListener.onTaskInfoChanged(taskInfo);
522             }
523             notifyLocusVisibilityIfNeeded(taskInfo);
524             if (updated || !taskInfo.equalsForCompatUi(data.getTaskInfo())) {
525                 // Notify the compat UI if the listener or task info changed.
526                 notifyCompatUI(taskInfo, newListener);
527             }
528             if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) {
529                 // Notify the recent tasks when a task changes windowing modes
530                 mRecentTasks.ifPresent(recentTasks ->
531                         recentTasks.onTaskWindowingModeChanged(taskInfo));
532             }
533             // TODO (b/207687679): Remove check for HOME once bug is fixed
534             final boolean isFocusedOrHome = taskInfo.isFocused
535                     || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
536                     && taskInfo.isVisible);
537             final boolean focusTaskChanged = (mLastFocusedTaskInfo == null
538                     || mLastFocusedTaskInfo.taskId != taskInfo.taskId
539                     || mLastFocusedTaskInfo.getWindowingMode() != taskInfo.getWindowingMode())
540                     && isFocusedOrHome;
541             if (focusTaskChanged) {
542                 for (int i = 0; i < mFocusListeners.size(); i++) {
543                     mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo);
544                 }
545                 mLastFocusedTaskInfo = taskInfo;
546             }
547         }
548     }
549 
550     @Override
onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)551     public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
552         synchronized (mLock) {
553             ProtoLog.v(WM_SHELL_TASK_ORG, "Task root back pressed taskId=%d", taskInfo.taskId);
554             final TaskListener listener = getTaskListener(taskInfo);
555             if (listener != null) {
556                 listener.onBackPressedOnTaskRoot(taskInfo);
557             }
558         }
559     }
560 
561     @Override
onTaskVanished(RunningTaskInfo taskInfo)562     public void onTaskVanished(RunningTaskInfo taskInfo) {
563         synchronized (mLock) {
564             ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId);
565             if (mUnfoldAnimationController != null) {
566                 mUnfoldAnimationController.onTaskVanished(taskInfo);
567             }
568 
569             final int taskId = taskInfo.taskId;
570             final TaskAppearedInfo appearedInfo = mTasks.get(taskId);
571             final TaskListener listener = getTaskListener(appearedInfo.getTaskInfo());
572             mTasks.remove(taskId);
573             if (listener != null) {
574                 listener.onTaskVanished(taskInfo);
575             }
576             notifyLocusVisibilityIfNeeded(taskInfo);
577             // Pass null for listener to remove the compat UI on this task if there is any.
578             notifyCompatUI(taskInfo, null /* taskListener */);
579             // Notify the recent tasks that a task has been removed
580             mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
581 
582             if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
583                 // Preemptively clean up the leash only if shell transitions are not enabled
584                 appearedInfo.getLeash().release();
585             }
586         }
587     }
588 
589     /**
590      * Return list of {@link RunningTaskInfo}s for the given display.
591      *
592      * @return filtered list of tasks or empty list
593      */
getRunningTasks(int displayId)594     public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
595         ArrayList<RunningTaskInfo> result = new ArrayList<>();
596         for (int i = 0; i < mTasks.size(); i++) {
597             RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
598             if (taskInfo.displayId == displayId) {
599                 result.add(taskInfo);
600             }
601         }
602         return result;
603     }
604 
605     /** Gets running task by taskId. Returns {@code null} if no such task observed. */
606     @Nullable
getRunningTaskInfo(int taskId)607     public RunningTaskInfo getRunningTaskInfo(int taskId) {
608         synchronized (mLock) {
609             final TaskAppearedInfo info = mTasks.get(taskId);
610             return info != null ? info.getTaskInfo() : null;
611         }
612     }
613 
614     /** Helper to set int metadata on the Surface corresponding to the task id. */
setSurfaceMetadata(int taskId, int key, int value)615     public void setSurfaceMetadata(int taskId, int key, int value) {
616         synchronized (mLock) {
617             final TaskAppearedInfo info = mTasks.get(taskId);
618             if (info == null || info.getLeash() == null) {
619                 return;
620             }
621             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
622             t.setMetadata(info.getLeash(), key, value);
623             t.apply();
624         }
625     }
626 
updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, TaskListener oldListener, TaskListener newListener)627     private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
628             TaskListener oldListener, TaskListener newListener) {
629         if (oldListener == newListener) return false;
630         // TODO: We currently send vanished/appeared as the task moves between types, but
631         //       we should consider adding a different mode-changed callback
632         if (oldListener != null) {
633             oldListener.onTaskVanished(taskInfo);
634         }
635         if (newListener != null) {
636             newListener.onTaskAppeared(taskInfo, leash);
637         }
638         return true;
639     }
640 
notifyLocusVisibilityIfNeeded(TaskInfo taskInfo)641     private void notifyLocusVisibilityIfNeeded(TaskInfo taskInfo) {
642         final int taskId = taskInfo.taskId;
643         final LocusId prevLocus = mVisibleTasksWithLocusId.get(taskId);
644         final boolean sameLocus = Objects.equals(prevLocus, taskInfo.mTopActivityLocusId);
645         if (prevLocus == null) {
646             // New visible locus
647             if (taskInfo.mTopActivityLocusId != null && taskInfo.isVisible) {
648                 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId);
649                 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */);
650             }
651         } else if (sameLocus && !taskInfo.isVisible) {
652             // Hidden locus
653             mVisibleTasksWithLocusId.remove(taskId);
654             notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, false /* visible */);
655         } else if (!sameLocus) {
656             // Changed locus
657             if (taskInfo.isVisible) {
658                 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId);
659                 notifyLocusIdChange(taskId, prevLocus, false /* visible */);
660                 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */);
661             } else {
662                 mVisibleTasksWithLocusId.remove(taskInfo.taskId);
663                 notifyLocusIdChange(taskId, prevLocus, false /* visible */);
664             }
665         }
666     }
667 
notifyLocusIdChange(int taskId, LocusId locus, boolean visible)668     private void notifyLocusIdChange(int taskId, LocusId locus, boolean visible) {
669         for (int i = 0; i < mLocusIdListeners.size(); i++) {
670             mLocusIdListeners.valueAt(i).onVisibilityChanged(taskId, locus, visible);
671         }
672     }
673 
674     @Override
onSizeCompatRestartButtonAppeared(int taskId)675     public void onSizeCompatRestartButtonAppeared(int taskId) {
676         final TaskAppearedInfo info;
677         synchronized (mLock) {
678             info = mTasks.get(taskId);
679         }
680         if (info == null) {
681             return;
682         }
683         logSizeCompatRestartButtonEventReported(info,
684                 FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
685     }
686 
687     @Override
onSizeCompatRestartButtonClicked(int taskId)688     public void onSizeCompatRestartButtonClicked(int taskId) {
689         final TaskAppearedInfo info;
690         synchronized (mLock) {
691             info = mTasks.get(taskId);
692         }
693         if (info == null) {
694             return;
695         }
696         logSizeCompatRestartButtonEventReported(info,
697                 FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__CLICKED);
698         restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
699     }
700 
701     @Override
onCameraControlStateUpdated( int taskId, @TaskInfo.CameraCompatControlState int state)702     public void onCameraControlStateUpdated(
703             int taskId, @TaskInfo.CameraCompatControlState int state) {
704         final TaskAppearedInfo info;
705         synchronized (mLock) {
706             info = mTasks.get(taskId);
707         }
708         if (info == null) {
709             return;
710         }
711         updateCameraCompatControlState(info.getTaskInfo().token, state);
712     }
713 
714     /** Reparents a child window surface to the task surface. */
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)715     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
716             SurfaceControl.Transaction t) {
717         final TaskListener taskListener;
718         synchronized (mLock) {
719             taskListener = mTasks.contains(taskId)
720                     ? getTaskListener(mTasks.get(taskId).getTaskInfo())
721                     : null;
722         }
723         if (taskListener == null) {
724             ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d",
725                     taskId);
726             return;
727         }
728         taskListener.reparentChildSurfaceToTask(taskId, sc, t);
729     }
730 
logSizeCompatRestartButtonEventReported(@onNull TaskAppearedInfo info, int event)731     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
732             int event) {
733         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
734         if (topActivityInfo == null) {
735             return;
736         }
737         FrameworkStatsLog.write(FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED,
738                 topActivityInfo.applicationInfo.uid, event);
739     }
740 
741     /**
742      * Notifies {@link CompatUIController} about the compat info changed on the give Task
743      * to update the UI accordingly.
744      *
745      * @param taskInfo the new Task info
746      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
747      *                     vanished.
748      */
notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener)749     private void notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
750         if (mCompatUI == null) {
751             return;
752         }
753 
754         // The task is vanished or doesn't support compat UI, notify to remove compat UI
755         // on this Task if there is any.
756         if (taskListener == null || !taskListener.supportCompatUI()
757                 || !taskInfo.hasCompatUI() || !taskInfo.isVisible) {
758             mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
759             return;
760         }
761         mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
762     }
763 
getTaskListener(RunningTaskInfo runningTaskInfo)764     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
765         return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
766     }
767 
getTaskListener(RunningTaskInfo runningTaskInfo, boolean removeLaunchCookieIfNeeded)768     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo,
769             boolean removeLaunchCookieIfNeeded) {
770 
771         final int taskId = runningTaskInfo.taskId;
772         TaskListener listener;
773 
774         // First priority goes to listener that might be pending for this task.
775         final ArrayList<IBinder> launchCookies = runningTaskInfo.launchCookies;
776         for (int i = launchCookies.size() - 1; i >= 0; --i) {
777             final IBinder cookie = launchCookies.get(i);
778             listener = mLaunchCookieToListener.get(cookie);
779             if (listener == null) continue;
780 
781             if (removeLaunchCookieIfNeeded) {
782                 // Remove the cookie and add the listener.
783                 mLaunchCookieToListener.remove(cookie);
784                 mTaskListeners.put(taskId, listener);
785             }
786             return listener;
787         }
788 
789         // Next priority goes to taskId specific listeners.
790         listener = mTaskListeners.get(taskId);
791         if (listener != null) return listener;
792 
793         // Next priority goes to the listener listening to its parent.
794         if (runningTaskInfo.hasParentTask()) {
795             listener = mTaskListeners.get(runningTaskInfo.parentTaskId);
796             if (listener != null) return listener;
797         }
798 
799         // Next we try type specific listeners.
800         final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo);
801         return mTaskListeners.get(taskListenerType);
802     }
803 
804     @VisibleForTesting
taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo)805     static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) {
806         switch (runningTaskInfo.getWindowingMode()) {
807             case WINDOWING_MODE_FULLSCREEN:
808                 return TASK_LISTENER_TYPE_FULLSCREEN;
809             case WINDOWING_MODE_MULTI_WINDOW:
810                 return TASK_LISTENER_TYPE_MULTI_WINDOW;
811             case WINDOWING_MODE_PINNED:
812                 return TASK_LISTENER_TYPE_PIP;
813             case WINDOWING_MODE_FREEFORM:
814                 return TASK_LISTENER_TYPE_FREEFORM;
815             case WINDOWING_MODE_UNDEFINED:
816             default:
817                 return TASK_LISTENER_TYPE_UNDEFINED;
818         }
819     }
820 
taskListenerTypeToString(@askListenerType int type)821     public static String taskListenerTypeToString(@TaskListenerType int type) {
822         switch (type) {
823             case TASK_LISTENER_TYPE_FULLSCREEN:
824                 return "TASK_LISTENER_TYPE_FULLSCREEN";
825             case TASK_LISTENER_TYPE_MULTI_WINDOW:
826                 return "TASK_LISTENER_TYPE_MULTI_WINDOW";
827             case TASK_LISTENER_TYPE_PIP:
828                 return "TASK_LISTENER_TYPE_PIP";
829             case TASK_LISTENER_TYPE_FREEFORM:
830                 return "TASK_LISTENER_TYPE_FREEFORM";
831             case TASK_LISTENER_TYPE_UNDEFINED:
832                 return "TASK_LISTENER_TYPE_UNDEFINED";
833             default:
834                 return "taskId#" + type;
835         }
836     }
837 
dump(@onNull PrintWriter pw, String prefix)838     public void dump(@NonNull PrintWriter pw, String prefix) {
839         synchronized (mLock) {
840             final String innerPrefix = prefix + "  ";
841             final String childPrefix = innerPrefix + "  ";
842             pw.println(prefix + TAG);
843             pw.println(innerPrefix + mTaskListeners.size() + " Listeners");
844             for (int i = mTaskListeners.size() - 1; i >= 0; --i) {
845                 final int key = mTaskListeners.keyAt(i);
846                 final TaskListener listener = mTaskListeners.valueAt(i);
847                 pw.println(innerPrefix + "#" + i + " " + taskListenerTypeToString(key));
848                 listener.dump(pw, childPrefix);
849             }
850 
851             pw.println();
852             pw.println(innerPrefix + mTasks.size() + " Tasks");
853             for (int i = mTasks.size() - 1; i >= 0; --i) {
854                 final int key = mTasks.keyAt(i);
855                 final TaskAppearedInfo info = mTasks.valueAt(i);
856                 final TaskListener listener = getTaskListener(info.getTaskInfo());
857                 final int windowingMode = info.getTaskInfo().getWindowingMode();
858                 String pkg = "";
859                 if (info.getTaskInfo().baseActivity != null) {
860                     pkg = info.getTaskInfo().baseActivity.getPackageName();
861                 }
862                 Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
863                 boolean running = info.getTaskInfo().isRunning;
864                 boolean visible = info.getTaskInfo().isVisible;
865                 boolean focused = info.getTaskInfo().isFocused;
866                 pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
867                         + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds
868                         + " running=" + running + " visible=" + visible + " focused=" + focused);
869             }
870 
871             pw.println();
872             pw.println(innerPrefix + mLaunchCookieToListener.size() + " Launch Cookies");
873             for (int i = mLaunchCookieToListener.size() - 1; i >= 0; --i) {
874                 final IBinder key = mLaunchCookieToListener.keyAt(i);
875                 final TaskListener listener = mLaunchCookieToListener.valueAt(i);
876                 pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
877             }
878 
879         }
880     }
881 }
882