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