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