1 /*
2  * Copyright (C) 2021 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.recents;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.content.pm.PackageManager.FEATURE_PC;
21 
22 import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
23 import static com.android.window.flags.Flags.enableTaskStackObserverInShell;
24 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
25 
26 import android.app.ActivityManager;
27 import android.app.ActivityTaskManager;
28 import android.app.IApplicationThread;
29 import android.app.PendingIntent;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.graphics.Color;
34 import android.os.Bundle;
35 import android.os.RemoteException;
36 import android.util.Slog;
37 import android.util.SparseArray;
38 import android.util.SparseIntArray;
39 import android.view.IRecentsAnimationRunner;
40 
41 import androidx.annotation.BinderThread;
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.VisibleForTesting;
45 
46 import com.android.internal.protolog.common.ProtoLog;
47 import com.android.wm.shell.common.ExternalInterfaceBinder;
48 import com.android.wm.shell.common.RemoteCallable;
49 import com.android.wm.shell.common.ShellExecutor;
50 import com.android.wm.shell.common.SingleInstanceRemoteListener;
51 import com.android.wm.shell.common.TaskStackListenerCallback;
52 import com.android.wm.shell.common.TaskStackListenerImpl;
53 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
54 import com.android.wm.shell.protolog.ShellProtoLogGroup;
55 import com.android.wm.shell.shared.DesktopModeStatus;
56 import com.android.wm.shell.shared.annotations.ExternalThread;
57 import com.android.wm.shell.shared.annotations.ShellMainThread;
58 import com.android.wm.shell.sysui.ShellCommandHandler;
59 import com.android.wm.shell.sysui.ShellController;
60 import com.android.wm.shell.sysui.ShellInit;
61 import com.android.wm.shell.transition.Transitions;
62 import com.android.wm.shell.util.GroupedRecentTaskInfo;
63 import com.android.wm.shell.util.SplitBounds;
64 
65 import java.io.PrintWriter;
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Optional;
71 import java.util.concurrent.Executor;
72 import java.util.function.Consumer;
73 
74 /**
75  * Manages the recent task list from the system, caching it as necessary.
76  */
77 public class RecentTasksController implements TaskStackListenerCallback,
78         RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener,
79         TaskStackTransitionObserver.TaskStackTransitionObserverListener {
80     private static final String TAG = RecentTasksController.class.getSimpleName();
81 
82     private final Context mContext;
83     private final ShellController mShellController;
84     private final ShellCommandHandler mShellCommandHandler;
85     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
86     private final ShellExecutor mMainExecutor;
87     private final TaskStackListenerImpl mTaskStackListener;
88     private final RecentTasksImpl mImpl = new RecentTasksImpl();
89     private final ActivityTaskManager mActivityTaskManager;
90     private final TaskStackTransitionObserver mTaskStackTransitionObserver;
91     private RecentsTransitionHandler mTransitionHandler = null;
92     private IRecentTasksListener mListener;
93     private final boolean mPcFeatureEnabled;
94 
95     // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
96     // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
97     private final SparseIntArray mSplitTasks = new SparseIntArray();
98     /**
99      * Maps taskId to {@link SplitBounds} for both taskIDs.
100      * Meaning there will be two taskId integers mapping to the same object.
101      * If there's any ordering to the pairing than we can probably just get away with only one
102      * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now.
103      */
104     private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
105 
106     /**
107      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
108      * supported.
109      */
110     @Nullable
create( Context context, ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, Optional<DesktopModeTaskRepository> desktopModeTaskRepository, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor )111     public static RecentTasksController create(
112             Context context,
113             ShellInit shellInit,
114             ShellController shellController,
115             ShellCommandHandler shellCommandHandler,
116             TaskStackListenerImpl taskStackListener,
117             ActivityTaskManager activityTaskManager,
118             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
119             TaskStackTransitionObserver taskStackTransitionObserver,
120             @ShellMainThread ShellExecutor mainExecutor
121     ) {
122         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
123             return null;
124         }
125         return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
126                 taskStackListener, activityTaskManager, desktopModeTaskRepository,
127                 taskStackTransitionObserver, mainExecutor);
128     }
129 
RecentTasksController(Context context, ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, Optional<DesktopModeTaskRepository> desktopModeTaskRepository, TaskStackTransitionObserver taskStackTransitionObserver, ShellExecutor mainExecutor)130     RecentTasksController(Context context,
131             ShellInit shellInit,
132             ShellController shellController,
133             ShellCommandHandler shellCommandHandler,
134             TaskStackListenerImpl taskStackListener,
135             ActivityTaskManager activityTaskManager,
136             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
137             TaskStackTransitionObserver taskStackTransitionObserver,
138             ShellExecutor mainExecutor) {
139         mContext = context;
140         mShellController = shellController;
141         mShellCommandHandler = shellCommandHandler;
142         mActivityTaskManager = activityTaskManager;
143         mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
144         mTaskStackListener = taskStackListener;
145         mDesktopModeTaskRepository = desktopModeTaskRepository;
146         mTaskStackTransitionObserver = taskStackTransitionObserver;
147         mMainExecutor = mainExecutor;
148         shellInit.addInitCallback(this::onInit, this);
149     }
150 
asRecentTasks()151     public RecentTasks asRecentTasks() {
152         return mImpl;
153     }
154 
createExternalInterface()155     private ExternalInterfaceBinder createExternalInterface() {
156         return new IRecentTasksImpl(this);
157     }
158 
onInit()159     private void onInit() {
160         mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
161                 this::createExternalInterface, this);
162         mShellCommandHandler.addDumpCallback(this::dump, this);
163         mTaskStackListener.addListener(this);
164         mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
165         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
166             mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
167                     mMainExecutor);
168         }
169     }
170 
setTransitionHandler(RecentsTransitionHandler handler)171     void setTransitionHandler(RecentsTransitionHandler handler) {
172         mTransitionHandler = handler;
173     }
174 
175     /**
176      * Adds a split pair. This call does not validate the taskIds, only that they are not the same.
177      */
addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds)178     public boolean addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
179         if (taskId1 == taskId2) {
180             return false;
181         }
182         if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2
183                 && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) {
184             // If the two tasks are already paired and the bounds are the same, then skip updating
185             return false;
186         }
187         // Remove any previous pairs
188         removeSplitPair(taskId1);
189         removeSplitPair(taskId2);
190         mTaskSplitBoundsMap.remove(taskId1);
191         mTaskSplitBoundsMap.remove(taskId2);
192 
193         mSplitTasks.put(taskId1, taskId2);
194         mSplitTasks.put(taskId2, taskId1);
195         mTaskSplitBoundsMap.put(taskId1, splitBounds);
196         mTaskSplitBoundsMap.put(taskId2, splitBounds);
197         notifyRecentTasksChanged();
198         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
199                 taskId1, taskId2, splitBounds);
200         return true;
201     }
202 
203     /**
204      * Removes a split pair.
205      */
removeSplitPair(int taskId)206     public void removeSplitPair(int taskId) {
207         int pairedTaskId = mSplitTasks.get(taskId, INVALID_TASK_ID);
208         if (pairedTaskId != INVALID_TASK_ID) {
209             mSplitTasks.delete(taskId);
210             mSplitTasks.delete(pairedTaskId);
211             mTaskSplitBoundsMap.remove(taskId);
212             mTaskSplitBoundsMap.remove(pairedTaskId);
213             notifyRecentTasksChanged();
214             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Remove split pair: %d, %d",
215                     taskId, pairedTaskId);
216         }
217     }
218 
219     @Nullable
getSplitBoundsForTaskId(int taskId)220     public SplitBounds getSplitBoundsForTaskId(int taskId) {
221         if (taskId == INVALID_TASK_ID) {
222             return null;
223         }
224 
225         // We could do extra verification of requiring both taskIds of a pair and verifying that
226         // the same split bounds object is returned... but meh. Seems unnecessary.
227         return mTaskSplitBoundsMap.get(taskId);
228     }
229 
230     @Override
getContext()231     public Context getContext() {
232         return mContext;
233     }
234 
235     @Override
getRemoteCallExecutor()236     public ShellExecutor getRemoteCallExecutor() {
237         return mMainExecutor;
238     }
239 
240     @Override
onTaskStackChanged()241     public void onTaskStackChanged() {
242         notifyRecentTasksChanged();
243     }
244 
245     @Override
onRecentTaskListUpdated()246     public void onRecentTaskListUpdated() {
247         // In some cases immediately after booting, the tasks in the system recent task list may be
248         // loaded, but not in the active task hierarchy in the system.  These tasks are displayed in
249         // overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
250         // callback (those are for changes to the active tasks), but the task list is still updated,
251         // so we should also invalidate the change id to ensure we load a new list instead of
252         // reusing a stale list.
253         notifyRecentTasksChanged();
254     }
255 
onTaskAdded(ActivityManager.RunningTaskInfo taskInfo)256     public void onTaskAdded(ActivityManager.RunningTaskInfo taskInfo) {
257         notifyRunningTaskAppeared(taskInfo);
258     }
259 
onTaskRemoved(ActivityManager.RunningTaskInfo taskInfo)260     public void onTaskRemoved(ActivityManager.RunningTaskInfo taskInfo) {
261         // Remove any split pairs associated with this task
262         removeSplitPair(taskInfo.taskId);
263         notifyRecentTasksChanged();
264         notifyRunningTaskVanished(taskInfo);
265     }
266 
267     /**
268      * Notify listeners that the running infos related to recent tasks was updated.
269      *
270      * This currently includes windowing mode and visibility.
271      */
onTaskRunningInfoChanged(ActivityManager.RunningTaskInfo taskInfo)272     public void onTaskRunningInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
273         notifyRecentTasksChanged();
274         notifyRunningTaskChanged(taskInfo);
275     }
276 
277     @Override
onActiveTasksChanged(int displayId)278     public void onActiveTasksChanged(int displayId) {
279         notifyRecentTasksChanged();
280     }
281 
282     @Override
onTaskMovedToFrontThroughTransition( ActivityManager.RunningTaskInfo runningTaskInfo)283     public void onTaskMovedToFrontThroughTransition(
284             ActivityManager.RunningTaskInfo runningTaskInfo) {
285         notifyTaskMovedToFront(runningTaskInfo);
286     }
287 
288     @VisibleForTesting
notifyRecentTasksChanged()289     void notifyRecentTasksChanged() {
290         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Notify recent tasks changed");
291         if (mListener == null) {
292             return;
293         }
294         try {
295             mListener.onRecentTasksChanged();
296         } catch (RemoteException e) {
297             Slog.w(TAG, "Failed call notifyRecentTasksChanged", e);
298         }
299     }
300 
301     /**
302      * Notify the running task listener that a task appeared on desktop environment.
303      */
notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)304     private void notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
305         if (mListener == null
306                 || !shouldEnableRunningTasksForDesktopMode()
307                 || taskInfo.realActivity == null) {
308             return;
309         }
310         try {
311             mListener.onRunningTaskAppeared(taskInfo);
312         } catch (RemoteException e) {
313             Slog.w(TAG, "Failed call onRunningTaskAppeared", e);
314         }
315     }
316 
317     /**
318      * Notify the running task listener that a task was removed on desktop environment.
319      */
notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo)320     private void notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
321         if (mListener == null
322                 || !shouldEnableRunningTasksForDesktopMode()
323                 || taskInfo.realActivity == null) {
324             return;
325         }
326         try {
327             mListener.onRunningTaskVanished(taskInfo);
328         } catch (RemoteException e) {
329             Slog.w(TAG, "Failed call onRunningTaskVanished", e);
330         }
331     }
332 
333     /**
334      * Notify the running task listener that a task was changed on desktop environment.
335      */
notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo)336     private void notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
337         if (mListener == null
338                 || !shouldEnableRunningTasksForDesktopMode()
339                 || taskInfo.realActivity == null) {
340             return;
341         }
342         try {
343             mListener.onRunningTaskChanged(taskInfo);
344         } catch (RemoteException e) {
345             Slog.w(TAG, "Failed call onRunningTaskChanged", e);
346         }
347     }
348 
notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)349     private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
350         if (mListener == null
351                 || !enableTaskStackObserverInShell()
352                 || taskInfo.realActivity == null) {
353             return;
354         }
355         try {
356             mListener.onTaskMovedToFront(taskInfo);
357         } catch (RemoteException e) {
358             Slog.w(TAG, "Failed call onTaskMovedToFront", e);
359         }
360     }
361 
shouldEnableRunningTasksForDesktopMode()362     private boolean shouldEnableRunningTasksForDesktopMode() {
363         return mPcFeatureEnabled
364                 || (DesktopModeStatus.canEnterDesktopMode(mContext)
365                 && enableDesktopWindowingTaskbarRunningApps());
366     }
367 
368     @VisibleForTesting
registerRecentTasksListener(IRecentTasksListener listener)369     void registerRecentTasksListener(IRecentTasksListener listener) {
370         mListener = listener;
371     }
372 
373     @VisibleForTesting
unregisterRecentTasksListener()374     void unregisterRecentTasksListener() {
375         mListener = null;
376     }
377 
378     @VisibleForTesting
hasRecentTasksListener()379     boolean hasRecentTasksListener() {
380         return mListener != null;
381     }
382 
383     @VisibleForTesting
getRecentTasks(int maxNum, int flags, int userId)384     ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
385         // Note: the returned task list is from the most-recent to least-recent order
386         final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
387                 maxNum, flags, userId);
388 
389         // Make a mapping of task id -> task info
390         final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
391         for (int i = 0; i < rawList.size(); i++) {
392             final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
393             rawMapping.put(taskInfo.taskId, taskInfo);
394         }
395 
396         ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
397 
398         int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
399 
400         // Pull out the pairs as we iterate back in the list
401         ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
402         for (int i = 0; i < rawList.size(); i++) {
403             final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
404             if (!rawMapping.contains(taskInfo.taskId)) {
405                 // If it's not in the mapping, then it was already paired with another task
406                 continue;
407             }
408 
409             if (DesktopModeStatus.canEnterDesktopMode(mContext)
410                     && mDesktopModeTaskRepository.isPresent()
411                     && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
412                 if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) {
413                     // Minimized freeform tasks should not be shown at all.
414                     continue;
415                 }
416                 // Freeform tasks will be added as a separate entry
417                 if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
418                     mostRecentFreeformTaskIndex = recentTasks.size();
419                 }
420                 freeformTasks.add(taskInfo);
421                 continue;
422             }
423 
424             final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
425             if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
426                     pairedTaskId)) {
427                 final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
428                 rawMapping.remove(pairedTaskId);
429                 recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
430                         mTaskSplitBoundsMap.get(pairedTaskId)));
431             } else {
432                 recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo));
433             }
434         }
435 
436         // Add a special entry for freeform tasks
437         if (!freeformTasks.isEmpty()) {
438             recentTasks.add(mostRecentFreeformTaskIndex, GroupedRecentTaskInfo.forFreeformTasks(
439                     freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
440         }
441 
442         return recentTasks;
443     }
444 
445     /**
446      * Returns the top running leaf task.
447      */
448     @Nullable
getTopRunningTask()449     public ActivityManager.RunningTaskInfo getTopRunningTask() {
450         List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
451                 false /* filterOnlyVisibleRecents */);
452         return tasks.isEmpty() ? null : tasks.get(0);
453     }
454 
455     /**
456      * Find the background task that match the given component.
457      */
458     @Nullable
findTaskInBackground(ComponentName componentName, int userId)459     public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
460             int userId) {
461         if (componentName == null) {
462             return null;
463         }
464         List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
465                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
466                 ActivityManager.getCurrentUser());
467         for (int i = 0; i < tasks.size(); i++) {
468             final ActivityManager.RecentTaskInfo task = tasks.get(i);
469             if (task.isVisible) {
470                 continue;
471             }
472             if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
473                 return task;
474             }
475         }
476         return null;
477     }
478 
479     /**
480      * Find the background task that match the given taskId.
481      */
482     @Nullable
findTaskInBackground(int taskId)483     public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
484         List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
485                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
486                 ActivityManager.getCurrentUser());
487         for (int i = 0; i < tasks.size(); i++) {
488             final ActivityManager.RecentTaskInfo task = tasks.get(i);
489             if (task.isVisible) {
490                 continue;
491             }
492             if (taskId == task.taskId) {
493                 return task;
494             }
495         }
496         return null;
497     }
498 
dump(@onNull PrintWriter pw, String prefix)499     public void dump(@NonNull PrintWriter pw, String prefix) {
500         final String innerPrefix = prefix + "  ";
501         pw.println(prefix + TAG);
502         pw.println(prefix + " mListener=" + mListener);
503         pw.println(prefix + "Tasks:");
504         ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
505                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
506         for (int i = 0; i < recentTasks.size(); i++) {
507             pw.println(innerPrefix + recentTasks.get(i));
508         }
509     }
510 
511     /**
512      * The interface for calls from outside the Shell, within the host process.
513      */
514     @ExternalThread
515     private class RecentTasksImpl implements RecentTasks {
516         @Override
getRecentTasks(int maxNum, int flags, int userId, Executor executor, Consumer<List<GroupedRecentTaskInfo>> callback)517         public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
518                 Consumer<List<GroupedRecentTaskInfo>> callback) {
519             mMainExecutor.execute(() -> {
520                 List<GroupedRecentTaskInfo> tasks =
521                         RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
522                 executor.execute(() -> callback.accept(tasks));
523             });
524         }
525 
526         @Override
addAnimationStateListener(Executor executor, Consumer<Boolean> listener)527         public void addAnimationStateListener(Executor executor, Consumer<Boolean> listener) {
528             mMainExecutor.execute(() -> {
529                 if (mTransitionHandler == null) {
530                     return;
531                 }
532                 mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
533                     @Override
534                     public void onAnimationStateChanged(boolean running) {
535                         executor.execute(() -> listener.accept(running));
536                     }
537                 });
538             });
539         }
540 
541         @Override
setTransitionBackgroundColor(@ullable Color color)542         public void setTransitionBackgroundColor(@Nullable Color color) {
543             mMainExecutor.execute(() -> {
544                 if (mTransitionHandler == null) {
545                     return;
546                 }
547                 mTransitionHandler.setTransitionBackgroundColor(color);
548             });
549         }
550     }
551 
552 
553     /**
554      * The interface for calls from outside the host process.
555      */
556     @BinderThread
557     private static class IRecentTasksImpl extends IRecentTasks.Stub
558             implements ExternalInterfaceBinder {
559         private RecentTasksController mController;
560         private final SingleInstanceRemoteListener<RecentTasksController,
561                 IRecentTasksListener> mListener;
562         private final IRecentTasksListener mRecentTasksListener = new IRecentTasksListener.Stub() {
563             @Override
564             public void onRecentTasksChanged() throws RemoteException {
565                 mListener.call(l -> l.onRecentTasksChanged());
566             }
567 
568             @Override
569             public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
570                 mListener.call(l -> l.onRunningTaskAppeared(taskInfo));
571             }
572 
573             @Override
574             public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
575                 mListener.call(l -> l.onRunningTaskVanished(taskInfo));
576             }
577 
578             @Override
579             public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
580                 mListener.call(l -> l.onRunningTaskChanged(taskInfo));
581             }
582 
583             @Override
584             public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
585                 mListener.call(l -> l.onTaskMovedToFront(taskInfo));
586             }
587         };
588 
IRecentTasksImpl(RecentTasksController controller)589         public IRecentTasksImpl(RecentTasksController controller) {
590             mController = controller;
591             mListener = new SingleInstanceRemoteListener<>(controller,
592                     c -> c.registerRecentTasksListener(mRecentTasksListener),
593                     c -> c.unregisterRecentTasksListener());
594         }
595 
596         /**
597          * Invalidates this instance, preventing future calls from updating the controller.
598          */
599         @Override
invalidate()600         public void invalidate() {
601             mController = null;
602             // Unregister the listener to ensure any registered binder death recipients are unlinked
603             mListener.unregister();
604         }
605 
606         @Override
registerRecentTasksListener(IRecentTasksListener listener)607         public void registerRecentTasksListener(IRecentTasksListener listener)
608                 throws RemoteException {
609             executeRemoteCallWithTaskPermission(mController, "registerRecentTasksListener",
610                     (controller) -> mListener.register(listener));
611         }
612 
613         @Override
unregisterRecentTasksListener(IRecentTasksListener listener)614         public void unregisterRecentTasksListener(IRecentTasksListener listener)
615                 throws RemoteException {
616             executeRemoteCallWithTaskPermission(mController, "unregisterRecentTasksListener",
617                     (controller) -> mListener.unregister());
618         }
619 
620         @Override
getRecentTasks(int maxNum, int flags, int userId)621         public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
622                 throws RemoteException {
623             if (mController == null) {
624                 // The controller is already invalidated -- just return an empty task list for now
625                 return new GroupedRecentTaskInfo[0];
626             }
627 
628             final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
629             executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
630                     (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
631                             .toArray(new GroupedRecentTaskInfo[0]),
632                     true /* blocking */);
633             return out[0];
634         }
635 
636         @Override
getRunningTasks(int maxNum)637         public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) {
638             final ActivityManager.RunningTaskInfo[][] tasks =
639                     new ActivityManager.RunningTaskInfo[][] {null};
640             executeRemoteCallWithTaskPermission(mController, "getRunningTasks",
641                     (controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum)
642                             .toArray(new ActivityManager.RunningTaskInfo[0]),
643                     true /* blocking */);
644             return tasks[0];
645         }
646 
647         @Override
startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener)648         public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
649                 IApplicationThread appThread, IRecentsAnimationRunner listener) {
650             if (mController.mTransitionHandler == null) {
651                 Slog.e(TAG, "Used shell-transitions startRecentsTransition without"
652                         + " shell-transitions");
653                 return;
654             }
655             executeRemoteCallWithTaskPermission(mController, "startRecentsTransition",
656                     (controller) -> controller.mTransitionHandler.startRecentsTransition(
657                             intent, fillIn, options, appThread, listener));
658         }
659     }
660 }
661