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