• 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;
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 
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager.RunningTaskInfo;
31 import android.app.TaskInfo;
32 import android.content.Context;
33 import android.content.LocusId;
34 import android.graphics.Rect;
35 import android.os.Binder;
36 import android.os.IBinder;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 import android.util.Log;
40 import android.util.SparseArray;
41 import android.view.SurfaceControl;
42 import android.window.ITaskOrganizerController;
43 import android.window.StartingWindowInfo;
44 import android.window.TaskAppearedInfo;
45 import android.window.TaskOrganizer;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.protolog.common.ProtoLog;
49 import com.android.wm.shell.common.ScreenshotUtils;
50 import com.android.wm.shell.common.ShellExecutor;
51 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
52 import com.android.wm.shell.startingsurface.StartingWindowController;
53 
54 import java.io.PrintWriter;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.List;
58 import java.util.Objects;
59 import java.util.function.Consumer;
60 
61 /**
62  * Unified task organizer for all components in the shell.
63  * TODO(b/167582004): may consider consolidating this class and TaskOrganizer
64  */
65 public class ShellTaskOrganizer extends TaskOrganizer implements
66         SizeCompatUIController.SizeCompatUICallback {
67 
68     // Intentionally using negative numbers here so the positive numbers can be used
69     // for task id specific listeners that will be added later.
70     public static final int TASK_LISTENER_TYPE_UNDEFINED = -1;
71     public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
72     public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
73     public static final int TASK_LISTENER_TYPE_PIP = -4;
74 
75     @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
76             TASK_LISTENER_TYPE_UNDEFINED,
77             TASK_LISTENER_TYPE_FULLSCREEN,
78             TASK_LISTENER_TYPE_MULTI_WINDOW,
79             TASK_LISTENER_TYPE_PIP,
80     })
81     public @interface TaskListenerType {}
82 
83     private static final String TAG = "ShellTaskOrganizer";
84 
85     /**
86      * Callbacks for when the tasks change in the system.
87      */
88     public interface TaskListener {
onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)89         default void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {}
onTaskInfoChanged(RunningTaskInfo taskInfo)90         default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
onTaskVanished(RunningTaskInfo taskInfo)91         default void onTaskVanished(RunningTaskInfo taskInfo) {}
onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)92         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
93         /** Whether this task listener supports size compat UI. */
supportSizeCompatUI()94         default boolean supportSizeCompatUI() {
95             // All TaskListeners should support size compat except PIP.
96             return true;
97         }
98         /** Attaches the a child window surface to the task surface. */
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)99         default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
100             throw new IllegalStateException(
101                     "This task listener doesn't support child surface attachment.");
102         }
dump(@onNull PrintWriter pw, String prefix)103         default void dump(@NonNull PrintWriter pw, String prefix) {};
104     }
105 
106     /**
107      * Callbacks for events on a task with a locus id.
108      */
109     public interface LocusIdListener {
110         /**
111          * Notifies when a task with a locusId becomes visible, when a visible task's locusId
112          * changes, or if a previously visible task with a locusId becomes invisible.
113          */
onVisibilityChanged(int taskId, LocusId locus, boolean visible)114         void onVisibilityChanged(int taskId, LocusId locus, boolean visible);
115     }
116 
117     /**
118      * Keys map from either a task id or {@link TaskListenerType}.
119      * @see #addListenerForTaskId
120      * @see #addListenerForType
121      */
122     private final SparseArray<TaskListener> mTaskListeners = new SparseArray<>();
123 
124     // Keeps track of all the tasks reported to this organizer (changes in windowing mode will
125     // require us to report to both old and new listeners)
126     private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>();
127 
128     /** @see #setPendingLaunchCookieListener */
129     private final ArrayMap<IBinder, TaskListener> mLaunchCookieToListener = new ArrayMap<>();
130 
131     // Keeps track of taskId's with visible locusIds. Used to notify any {@link LocusIdListener}s
132     // that might be set.
133     private final SparseArray<LocusId> mVisibleTasksWithLocusId = new SparseArray<>();
134 
135     /** @see #addLocusIdListener */
136     private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
137 
138     private final Object mLock = new Object();
139     private StartingWindowController mStartingWindow;
140 
141     /**
142      * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
143      * compat.
144      */
145     @Nullable
146     private final SizeCompatUIController mSizeCompatUI;
147 
ShellTaskOrganizer(ShellExecutor mainExecutor, Context context)148     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
149         this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
150     }
151 
ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI)152     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
153             SizeCompatUIController sizeCompatUI) {
154         this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
155     }
156 
157     @VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI)158     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
159             Context context, @Nullable SizeCompatUIController sizeCompatUI) {
160         super(taskOrganizerController, mainExecutor);
161         mSizeCompatUI = sizeCompatUI;
162         if (sizeCompatUI != null) {
163             sizeCompatUI.setSizeCompatUICallback(this);
164         }
165     }
166 
167     @Override
registerOrganizer()168     public List<TaskAppearedInfo> registerOrganizer() {
169         synchronized (mLock) {
170             ProtoLog.v(WM_SHELL_TASK_ORG, "Registering organizer");
171             final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
172             for (int i = 0; i < taskInfos.size(); i++) {
173                 final TaskAppearedInfo info = taskInfos.get(i);
174                 ProtoLog.v(WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s",
175                         info.getTaskInfo().taskId, info.getTaskInfo().baseIntent);
176                 onTaskAppeared(info);
177             }
178             return taskInfos;
179         }
180     }
181 
createRootTask(int displayId, int windowingMode, TaskListener listener)182     public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
183         ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s",
184                 displayId, windowingMode, listener.toString());
185         final IBinder cookie = new Binder();
186         setPendingLaunchCookieListener(cookie, listener);
187         super.createRootTask(displayId, windowingMode, cookie);
188     }
189 
190     /**
191      * @hide
192      */
initStartingWindow(StartingWindowController startingWindow)193     public void initStartingWindow(StartingWindowController startingWindow) {
194         mStartingWindow = startingWindow;
195     }
196 
197     /**
198      * Adds a listener for a specific task id.
199      */
addListenerForTaskId(TaskListener listener, int taskId)200     public void addListenerForTaskId(TaskListener listener, int taskId) {
201         synchronized (mLock) {
202             ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForTaskId taskId=%s", taskId);
203             if (mTaskListeners.get(taskId) != null) {
204                 throw new IllegalArgumentException(
205                         "Listener for taskId=" + taskId + " already exists");
206             }
207 
208             final TaskAppearedInfo info = mTasks.get(taskId);
209             if (info == null) {
210                 throw new IllegalArgumentException("addListenerForTaskId unknown taskId=" + taskId);
211             }
212 
213             final TaskListener oldListener = getTaskListener(info.getTaskInfo());
214             mTaskListeners.put(taskId, listener);
215             updateTaskListenerIfNeeded(info.getTaskInfo(), info.getLeash(), oldListener, listener);
216         }
217     }
218 
219     /**
220      * Adds a listener for tasks with given types.
221      */
addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes)222     public void addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes) {
223         synchronized (mLock) {
224             ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForType types=%s listener=%s",
225                     Arrays.toString(listenerTypes), listener);
226             for (int listenerType : listenerTypes) {
227                 if (mTaskListeners.get(listenerType) != null) {
228                     throw new IllegalArgumentException("Listener for listenerType=" + listenerType
229                             + " already exists");
230                 }
231                 mTaskListeners.put(listenerType, listener);
232 
233                 // Notify the listener of all existing tasks with the given type.
234                 for (int i = mTasks.size() - 1; i >= 0; --i) {
235                     final TaskAppearedInfo data = mTasks.valueAt(i);
236                     final TaskListener taskListener = getTaskListener(data.getTaskInfo());
237                     if (taskListener != listener) continue;
238                     listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
239                 }
240             }
241         }
242     }
243 
244     /**
245      * Removes a registered listener.
246      */
removeListener(TaskListener listener)247     public void removeListener(TaskListener listener) {
248         synchronized (mLock) {
249             ProtoLog.v(WM_SHELL_TASK_ORG, "Remove listener=%s", listener);
250             final int index = mTaskListeners.indexOfValue(listener);
251             if (index == -1) {
252                 Log.w(TAG, "No registered listener found");
253                 return;
254             }
255 
256             // Collect tasks associated with the listener we are about to remove.
257             final ArrayList<TaskAppearedInfo> tasks = new ArrayList<>();
258             for (int i = mTasks.size() - 1; i >= 0; --i) {
259                 final TaskAppearedInfo data = mTasks.valueAt(i);
260                 final TaskListener taskListener = getTaskListener(data.getTaskInfo());
261                 if (taskListener != listener) continue;
262                 tasks.add(data);
263             }
264 
265             // Remove listener
266             mTaskListeners.removeAt(index);
267 
268             // Associate tasks with new listeners if needed.
269             for (int i = tasks.size() - 1; i >= 0; --i) {
270                 final TaskAppearedInfo data = tasks.get(i);
271                 updateTaskListenerIfNeeded(data.getTaskInfo(), data.getLeash(),
272                         null /* oldListener already removed*/, getTaskListener(data.getTaskInfo()));
273             }
274         }
275     }
276 
277     /**
278      * Associated a listener to a pending launch cookie so we can route the task later once it
279      * appears.
280      */
setPendingLaunchCookieListener(IBinder cookie, TaskListener listener)281     public void setPendingLaunchCookieListener(IBinder cookie, TaskListener listener) {
282         synchronized (mLock) {
283             mLaunchCookieToListener.put(cookie, listener);
284         }
285     }
286 
287     /**
288      * Adds a listener to be notified for {@link LocusId} visibility changes.
289      */
addLocusIdListener(LocusIdListener listener)290     public void addLocusIdListener(LocusIdListener listener) {
291         synchronized (mLock) {
292             mLocusIdListeners.add(listener);
293             for (int i = 0; i < mVisibleTasksWithLocusId.size(); i++) {
294                 listener.onVisibilityChanged(mVisibleTasksWithLocusId.keyAt(i),
295                         mVisibleTasksWithLocusId.valueAt(i), true /* visible */);
296             }
297         }
298     }
299 
300     /**
301      * Removes listener.
302      */
removeLocusIdListener(LocusIdListener listener)303     public void removeLocusIdListener(LocusIdListener listener) {
304         synchronized (mLock) {
305             mLocusIdListeners.remove(listener);
306         }
307     }
308 
309     @Override
addStartingWindow(StartingWindowInfo info, IBinder appToken)310     public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
311         if (mStartingWindow != null) {
312             mStartingWindow.addStartingWindow(info, appToken);
313         }
314     }
315 
316     @Override
removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, boolean playRevealAnimation)317     public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
318             boolean playRevealAnimation) {
319         if (mStartingWindow != null) {
320             mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
321         }
322     }
323 
324     @Override
copySplashScreenView(int taskId)325     public void copySplashScreenView(int taskId) {
326         if (mStartingWindow != null) {
327             mStartingWindow.copySplashScreenView(taskId);
328         }
329     }
330 
331     @Override
onAppSplashScreenViewRemoved(int taskId)332     public void onAppSplashScreenViewRemoved(int taskId) {
333         if (mStartingWindow != null) {
334             mStartingWindow.onAppSplashScreenViewRemoved(taskId);
335         }
336     }
337 
338     @Override
onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)339     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
340         synchronized (mLock) {
341             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
342         }
343     }
344 
onTaskAppeared(TaskAppearedInfo info)345     private void onTaskAppeared(TaskAppearedInfo info) {
346         final int taskId = info.getTaskInfo().taskId;
347         mTasks.put(taskId, info);
348         final TaskListener listener =
349                 getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/);
350         ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener);
351         if (listener != null) {
352             listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
353         }
354         notifyLocusVisibilityIfNeeded(info.getTaskInfo());
355         notifySizeCompatUI(info.getTaskInfo(), listener);
356     }
357 
358     /**
359      * Take a screenshot of a task.
360      */
screenshotTask(RunningTaskInfo taskInfo, Rect crop, Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer)361     public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
362             Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
363         final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
364         if (info == null) {
365             return;
366         }
367         ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
368     }
369 
370 
371     @Override
onTaskInfoChanged(RunningTaskInfo taskInfo)372     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
373         synchronized (mLock) {
374             ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
375             final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
376             final TaskListener oldListener = getTaskListener(data.getTaskInfo());
377             final TaskListener newListener = getTaskListener(taskInfo);
378             mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
379             final boolean updated = updateTaskListenerIfNeeded(
380                     taskInfo, data.getLeash(), oldListener, newListener);
381             if (!updated && newListener != null) {
382                 newListener.onTaskInfoChanged(taskInfo);
383             }
384             notifyLocusVisibilityIfNeeded(taskInfo);
385             if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
386                 // Notify the size compat UI if the listener or task info changed.
387                 notifySizeCompatUI(taskInfo, newListener);
388             }
389         }
390     }
391 
392     @Override
onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)393     public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
394         synchronized (mLock) {
395             ProtoLog.v(WM_SHELL_TASK_ORG, "Task root back pressed taskId=%d", taskInfo.taskId);
396             final TaskListener listener = getTaskListener(taskInfo);
397             if (listener != null) {
398                 listener.onBackPressedOnTaskRoot(taskInfo);
399             }
400         }
401     }
402 
403     @Override
onTaskVanished(RunningTaskInfo taskInfo)404     public void onTaskVanished(RunningTaskInfo taskInfo) {
405         synchronized (mLock) {
406             ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId);
407             final int taskId = taskInfo.taskId;
408             final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo());
409             mTasks.remove(taskId);
410             if (listener != null) {
411                 listener.onTaskVanished(taskInfo);
412             }
413             notifyLocusVisibilityIfNeeded(taskInfo);
414             // Pass null for listener to remove the size compat UI on this task if there is any.
415             notifySizeCompatUI(taskInfo, null /* taskListener */);
416         }
417     }
418 
419     /** Gets running task by taskId. Returns {@code null} if no such task observed. */
420     @Nullable
getRunningTaskInfo(int taskId)421     public RunningTaskInfo getRunningTaskInfo(int taskId) {
422         synchronized (mLock) {
423             final TaskAppearedInfo info = mTasks.get(taskId);
424             return info != null ? info.getTaskInfo() : null;
425         }
426     }
427 
428     /** Helper to set int metadata on the Surface corresponding to the task id. */
setSurfaceMetadata(int taskId, int key, int value)429     public void setSurfaceMetadata(int taskId, int key, int value) {
430         synchronized (mLock) {
431             final TaskAppearedInfo info = mTasks.get(taskId);
432             if (info == null || info.getLeash() == null) {
433                 return;
434             }
435             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
436             t.setMetadata(info.getLeash(), key, value);
437             t.apply();
438         }
439     }
440 
updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, TaskListener oldListener, TaskListener newListener)441     private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
442             TaskListener oldListener, TaskListener newListener) {
443         if (oldListener == newListener) return false;
444         // TODO: We currently send vanished/appeared as the task moves between types, but
445         //       we should consider adding a different mode-changed callback
446         if (oldListener != null) {
447             oldListener.onTaskVanished(taskInfo);
448         }
449         if (newListener != null) {
450             newListener.onTaskAppeared(taskInfo, leash);
451         }
452         return true;
453     }
454 
notifyLocusVisibilityIfNeeded(TaskInfo taskInfo)455     private void notifyLocusVisibilityIfNeeded(TaskInfo taskInfo) {
456         final int taskId = taskInfo.taskId;
457         final LocusId prevLocus = mVisibleTasksWithLocusId.get(taskId);
458         final boolean sameLocus = Objects.equals(prevLocus, taskInfo.mTopActivityLocusId);
459         if (prevLocus == null) {
460             // New visible locus
461             if (taskInfo.mTopActivityLocusId != null && taskInfo.isVisible) {
462                 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId);
463                 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */);
464             }
465         } else if (sameLocus && !taskInfo.isVisible) {
466             // Hidden locus
467             mVisibleTasksWithLocusId.remove(taskId);
468             notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, false /* visible */);
469         } else if (!sameLocus) {
470             // Changed locus
471             if (taskInfo.isVisible) {
472                 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId);
473                 notifyLocusIdChange(taskId, prevLocus, false /* visible */);
474                 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */);
475             } else {
476                 mVisibleTasksWithLocusId.remove(taskInfo.taskId);
477                 notifyLocusIdChange(taskId, prevLocus, false /* visible */);
478             }
479         }
480     }
481 
notifyLocusIdChange(int taskId, LocusId locus, boolean visible)482     private void notifyLocusIdChange(int taskId, LocusId locus, boolean visible) {
483         for (int i = 0; i < mLocusIdListeners.size(); i++) {
484             mLocusIdListeners.valueAt(i).onVisibilityChanged(taskId, locus, visible);
485         }
486     }
487 
488     @Override
onSizeCompatRestartButtonClicked(int taskId)489     public void onSizeCompatRestartButtonClicked(int taskId) {
490         final TaskAppearedInfo info;
491         synchronized (mLock) {
492             info = mTasks.get(taskId);
493         }
494         if (info != null) {
495             restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
496         }
497     }
498 
499     /**
500      * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
501      * to update the UI accordingly.
502      *
503      * @param taskInfo the new Task info
504      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
505      *                     vanished.
506      */
notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener)507     private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
508         if (mSizeCompatUI == null) {
509             return;
510         }
511 
512         // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
513         // on this Task if there is any.
514         if (taskListener == null || !taskListener.supportSizeCompatUI()
515                 || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
516             mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
517                     null /* taskConfig */, null /* taskListener */);
518             return;
519         }
520 
521         mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
522                 taskInfo.configuration, taskListener);
523     }
524 
getTaskListener(RunningTaskInfo runningTaskInfo)525     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
526         return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
527     }
528 
getTaskListener(RunningTaskInfo runningTaskInfo, boolean removeLaunchCookieIfNeeded)529     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo,
530             boolean removeLaunchCookieIfNeeded) {
531 
532         final int taskId = runningTaskInfo.taskId;
533         TaskListener listener;
534 
535         // First priority goes to listener that might be pending for this task.
536         final ArrayList<IBinder> launchCookies = runningTaskInfo.launchCookies;
537         for (int i = launchCookies.size() - 1; i >= 0; --i) {
538             final IBinder cookie = launchCookies.get(i);
539             listener = mLaunchCookieToListener.get(cookie);
540             if (listener == null) continue;
541 
542             if (removeLaunchCookieIfNeeded) {
543                 // Remove the cookie and add the listener.
544                 mLaunchCookieToListener.remove(cookie);
545                 mTaskListeners.put(taskId, listener);
546             }
547             return listener;
548         }
549 
550         // Next priority goes to taskId specific listeners.
551         listener = mTaskListeners.get(taskId);
552         if (listener != null) return listener;
553 
554         // Next priority goes to the listener listening to its parent.
555         if (runningTaskInfo.hasParentTask()) {
556             listener = mTaskListeners.get(runningTaskInfo.parentTaskId);
557             if (listener != null) return listener;
558         }
559 
560         // Next we try type specific listeners.
561         final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo);
562         return mTaskListeners.get(taskListenerType);
563     }
564 
565     @VisibleForTesting
taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo)566     static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) {
567         switch (runningTaskInfo.getWindowingMode()) {
568             case WINDOWING_MODE_FULLSCREEN:
569                 return TASK_LISTENER_TYPE_FULLSCREEN;
570             case WINDOWING_MODE_MULTI_WINDOW:
571                 return TASK_LISTENER_TYPE_MULTI_WINDOW;
572             case WINDOWING_MODE_PINNED:
573                 return TASK_LISTENER_TYPE_PIP;
574             case WINDOWING_MODE_FREEFORM:
575             case WINDOWING_MODE_UNDEFINED:
576             default:
577                 return TASK_LISTENER_TYPE_UNDEFINED;
578         }
579     }
580 
taskListenerTypeToString(@askListenerType int type)581     public static String taskListenerTypeToString(@TaskListenerType int type) {
582         switch (type) {
583             case TASK_LISTENER_TYPE_FULLSCREEN:
584                 return "TASK_LISTENER_TYPE_FULLSCREEN";
585             case TASK_LISTENER_TYPE_MULTI_WINDOW:
586                 return "TASK_LISTENER_TYPE_MULTI_WINDOW";
587             case TASK_LISTENER_TYPE_PIP:
588                 return "TASK_LISTENER_TYPE_PIP";
589             case TASK_LISTENER_TYPE_UNDEFINED:
590                 return "TASK_LISTENER_TYPE_UNDEFINED";
591             default:
592                 return "taskId#" + type;
593         }
594     }
595 
dump(@onNull PrintWriter pw, String prefix)596     public void dump(@NonNull PrintWriter pw, String prefix) {
597         synchronized (mLock) {
598             final String innerPrefix = prefix + "  ";
599             final String childPrefix = innerPrefix + "  ";
600             pw.println(prefix + TAG);
601             pw.println(innerPrefix + mTaskListeners.size() + " Listeners");
602             for (int i = mTaskListeners.size() - 1; i >= 0; --i) {
603                 final int key = mTaskListeners.keyAt(i);
604                 final TaskListener listener = mTaskListeners.valueAt(i);
605                 pw.println(innerPrefix + "#" + i + " " + taskListenerTypeToString(key));
606                 listener.dump(pw, childPrefix);
607             }
608 
609             pw.println();
610             pw.println(innerPrefix + mTasks.size() + " Tasks");
611             for (int i = mTasks.size() - 1; i >= 0; --i) {
612                 final int key = mTasks.keyAt(i);
613                 final TaskAppearedInfo info = mTasks.valueAt(i);
614                 final TaskListener listener = getTaskListener(info.getTaskInfo());
615                 pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
616             }
617 
618             pw.println();
619             pw.println(innerPrefix + mLaunchCookieToListener.size() + " Launch Cookies");
620             for (int i = mLaunchCookieToListener.size() - 1; i >= 0; --i) {
621                 final IBinder key = mLaunchCookieToListener.keyAt(i);
622                 final TaskListener listener = mLaunchCookieToListener.valueAt(i);
623                 pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
624             }
625         }
626     }
627 }
628