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