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.splitscreen; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 23 import static android.view.RemoteAnimationTarget.MODE_OPENING; 24 25 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; 26 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; 27 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; 28 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; 29 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 30 31 import android.annotation.CallSuper; 32 import android.annotation.Nullable; 33 import android.app.ActivityManager; 34 import android.content.Context; 35 import android.graphics.Point; 36 import android.graphics.Rect; 37 import android.os.IBinder; 38 import android.util.Slog; 39 import android.util.SparseArray; 40 import android.view.RemoteAnimationTarget; 41 import android.view.SurfaceControl; 42 import android.view.SurfaceSession; 43 import android.window.WindowContainerToken; 44 import android.window.WindowContainerTransaction; 45 46 import androidx.annotation.NonNull; 47 48 import com.android.internal.protolog.common.ProtoLog; 49 import com.android.internal.util.ArrayUtils; 50 import com.android.launcher3.icons.IconProvider; 51 import com.android.wm.shell.ShellTaskOrganizer; 52 import com.android.wm.shell.common.SurfaceUtils; 53 import com.android.wm.shell.common.SyncTransactionQueue; 54 import com.android.wm.shell.common.split.SplitDecorManager; 55 import com.android.wm.shell.splitscreen.SplitScreen.StageType; 56 import com.android.wm.shell.windowdecor.WindowDecorViewModel; 57 58 import java.io.PrintWriter; 59 import java.util.Optional; 60 import java.util.function.Consumer; 61 import java.util.function.Predicate; 62 63 /** 64 * Base class that handle common task org. related for split-screen stages. 65 * Note that this class and its sub-class do not directly perform hierarchy operations. 66 * They only serve to hold a collection of tasks and provide APIs like 67 * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized 68 * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers. 69 * 70 * @see StageCoordinator 71 */ 72 class StageTaskListener implements ShellTaskOrganizer.TaskListener { 73 private static final String TAG = StageTaskListener.class.getSimpleName(); 74 75 /** Callback interface for listening to changes in a split-screen stage. */ 76 public interface StageListenerCallbacks { onRootTaskAppeared()77 void onRootTaskAppeared(); 78 onChildTaskAppeared(int taskId)79 void onChildTaskAppeared(int taskId); 80 onStatusChanged(boolean visible, boolean hasChildren)81 void onStatusChanged(boolean visible, boolean hasChildren); 82 onChildTaskStatusChanged(int taskId, boolean present, boolean visible)83 void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); 84 onRootTaskVanished()85 void onRootTaskVanished(); 86 onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo)87 void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); 88 } 89 90 private final Context mContext; 91 private final StageListenerCallbacks mCallbacks; 92 private final SurfaceSession mSurfaceSession; 93 private final SyncTransactionQueue mSyncQueue; 94 private final IconProvider mIconProvider; 95 private final Optional<WindowDecorViewModel> mWindowDecorViewModel; 96 97 protected ActivityManager.RunningTaskInfo mRootTaskInfo; 98 protected SurfaceControl mRootLeash; 99 protected SurfaceControl mDimLayer; 100 protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); 101 private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); 102 // TODO(b/204308910): Extracts SplitDecorManager related code to common package. 103 private SplitDecorManager mSplitDecorManager; 104 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, IconProvider iconProvider, Optional<WindowDecorViewModel> windowDecorViewModel)105 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, 106 StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, 107 SurfaceSession surfaceSession, IconProvider iconProvider, 108 Optional<WindowDecorViewModel> windowDecorViewModel) { 109 mContext = context; 110 mCallbacks = callbacks; 111 mSyncQueue = syncQueue; 112 mSurfaceSession = surfaceSession; 113 mIconProvider = iconProvider; 114 mWindowDecorViewModel = windowDecorViewModel; 115 taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); 116 } 117 getChildCount()118 int getChildCount() { 119 return mChildrenTaskInfo.size(); 120 } 121 containsTask(int taskId)122 boolean containsTask(int taskId) { 123 return mChildrenTaskInfo.contains(taskId); 124 } 125 containsToken(WindowContainerToken token)126 boolean containsToken(WindowContainerToken token) { 127 return contains(t -> t.token.equals(token)); 128 } 129 containsContainer(IBinder binder)130 boolean containsContainer(IBinder binder) { 131 return contains(t -> t.token.asBinder() == binder); 132 } 133 134 /** 135 * Returns the top visible child task's id. 136 */ getTopVisibleChildTaskId()137 int getTopVisibleChildTaskId() { 138 final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible 139 && t.isVisibleRequested); 140 return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID; 141 } 142 143 /** 144 * Returns the top activity uid for the top child task. 145 */ getTopChildTaskUid()146 int getTopChildTaskUid() { 147 final ActivityManager.RunningTaskInfo taskInfo = 148 getChildTaskInfo(t -> t.topActivityInfo != null); 149 return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0; 150 } 151 152 /** @return {@code true} if this listener contains the currently focused task. */ isFocused()153 boolean isFocused() { 154 return contains(t -> t.isFocused); 155 } 156 contains(Predicate<ActivityManager.RunningTaskInfo> predicate)157 private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) { 158 if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) { 159 return true; 160 } 161 162 return getChildTaskInfo(predicate) != null; 163 } 164 165 @Nullable getChildTaskInfo( Predicate<ActivityManager.RunningTaskInfo> predicate)166 private ActivityManager.RunningTaskInfo getChildTaskInfo( 167 Predicate<ActivityManager.RunningTaskInfo> predicate) { 168 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 169 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 170 if (predicate.test(taskInfo)) { 171 return taskInfo; 172 } 173 } 174 return null; 175 } 176 177 @Override 178 @CallSuper onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)179 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 180 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d " 181 + "taskActivity=%s", 182 taskInfo.taskId, taskInfo.parentTaskId, 183 mRootTaskInfo != null ? mRootTaskInfo.taskId : -1, 184 taskInfo.baseActivity); 185 if (mRootTaskInfo == null) { 186 mRootLeash = leash; 187 mRootTaskInfo = taskInfo; 188 mSplitDecorManager = new SplitDecorManager( 189 mRootTaskInfo.configuration, 190 mIconProvider, 191 mSurfaceSession); 192 mCallbacks.onRootTaskAppeared(); 193 sendStatusChanged(); 194 mSyncQueue.runInSync(t -> mDimLayer = 195 SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession)); 196 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 197 final int taskId = taskInfo.taskId; 198 mChildrenLeashes.put(taskId, leash); 199 mChildrenTaskInfo.put(taskId, taskInfo); 200 mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, 201 taskInfo.isVisible && taskInfo.isVisibleRequested); 202 if (ENABLE_SHELL_TRANSITIONS) { 203 // Status is managed/synchronized by the transition lifecycle. 204 return; 205 } 206 updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); 207 mCallbacks.onChildTaskAppeared(taskId); 208 sendStatusChanged(); 209 } else { 210 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 211 + "\n mRootTaskInfo: " + mRootTaskInfo); 212 } 213 } 214 215 @Override 216 @CallSuper onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)217 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 218 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s", 219 taskInfo.taskId, taskInfo.baseActivity); 220 mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); 221 if (mRootTaskInfo.taskId == taskInfo.taskId) { 222 // Inflates split decor view only when the root task is visible. 223 if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { 224 if (taskInfo.isVisible) { 225 mSplitDecorManager.inflate(mContext, mRootLeash); 226 } else { 227 mSyncQueue.runInSync(t -> mSplitDecorManager.release(t)); 228 } 229 } 230 mRootTaskInfo = taskInfo; 231 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 232 if (!taskInfo.supportsMultiWindow 233 || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) 234 || !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, 235 taskInfo.getWindowingMode())) { 236 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, 237 "onTaskInfoChanged: task=%d no longer supports multiwindow", 238 taskInfo.taskId); 239 // Leave split screen if the task no longer supports multi window or have 240 // uncontrolled task. 241 mCallbacks.onNoLongerSupportMultiWindow(taskInfo); 242 return; 243 } 244 mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); 245 mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, 246 taskInfo.isVisible && taskInfo.isVisibleRequested); 247 if (!ENABLE_SHELL_TRANSITIONS) { 248 updateChildTaskSurface( 249 taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); 250 } 251 } else { 252 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 253 + "\n mRootTaskInfo: " + mRootTaskInfo); 254 } 255 if (ENABLE_SHELL_TRANSITIONS) { 256 // Status is managed/synchronized by the transition lifecycle. 257 return; 258 } 259 sendStatusChanged(); 260 } 261 262 @Override 263 @CallSuper onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)264 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 265 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId); 266 final int taskId = taskInfo.taskId; 267 mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo)); 268 if (mRootTaskInfo.taskId == taskId) { 269 mCallbacks.onRootTaskVanished(); 270 mRootTaskInfo = null; 271 mRootLeash = null; 272 mSyncQueue.runInSync(t -> { 273 t.remove(mDimLayer); 274 mSplitDecorManager.release(t); 275 }); 276 } else if (mChildrenTaskInfo.contains(taskId)) { 277 mChildrenTaskInfo.remove(taskId); 278 mChildrenLeashes.remove(taskId); 279 mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); 280 if (ENABLE_SHELL_TRANSITIONS) { 281 // Status is managed/synchronized by the transition lifecycle. 282 return; 283 } 284 sendStatusChanged(); 285 } else { 286 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 287 + "\n mRootTaskInfo: " + mRootTaskInfo); 288 } 289 } 290 291 @Override attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)292 public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 293 b.setParent(findTaskSurface(taskId)); 294 } 295 296 @Override reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)297 public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, 298 SurfaceControl.Transaction t) { 299 t.reparent(sc, findTaskSurface(taskId)); 300 } 301 findTaskSurface(int taskId)302 private SurfaceControl findTaskSurface(int taskId) { 303 if (mRootTaskInfo.taskId == taskId) { 304 return mRootLeash; 305 } else if (mChildrenLeashes.contains(taskId)) { 306 return mChildrenLeashes.get(taskId); 307 } else { 308 throw new IllegalArgumentException("There is no surface for taskId=" + taskId); 309 } 310 } 311 isRootTaskId(int taskId)312 boolean isRootTaskId(int taskId) { 313 return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; 314 } 315 onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately, float[] veilColor)316 void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, 317 int offsetY, boolean immediately, float[] veilColor) { 318 if (mSplitDecorManager != null && mRootTaskInfo != null) { 319 mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, 320 offsetY, immediately, veilColor); 321 } 322 } 323 onResized(SurfaceControl.Transaction t)324 void onResized(SurfaceControl.Transaction t) { 325 if (mSplitDecorManager != null) { 326 mSplitDecorManager.onResized(t, null); 327 } 328 } 329 screenshotIfNeeded(SurfaceControl.Transaction t)330 void screenshotIfNeeded(SurfaceControl.Transaction t) { 331 if (mSplitDecorManager != null) { 332 mSplitDecorManager.screenshotIfNeeded(t); 333 } 334 } 335 fadeOutDecor(Runnable finishedCallback)336 void fadeOutDecor(Runnable finishedCallback) { 337 if (mSplitDecorManager != null) { 338 mSplitDecorManager.fadeOutDecor(finishedCallback); 339 } else { 340 finishedCallback.run(); 341 } 342 } 343 getSplitDecorManager()344 SplitDecorManager getSplitDecorManager() { 345 return mSplitDecorManager; 346 } 347 addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct)348 void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { 349 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addTask: task=%d", task.taskId); 350 // Clear overridden bounds and windowing mode to make sure the child task can inherit 351 // windowing mode and bounds from split root. 352 wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) 353 .setBounds(task.token, null); 354 355 wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/); 356 } 357 reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct)358 void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { 359 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b", taskId, onTop); 360 if (!containsTask(taskId)) { 361 return; 362 } 363 wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); 364 } 365 doForAllChildTasks(Consumer<Integer> consumer)366 void doForAllChildTasks(Consumer<Integer> consumer) { 367 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 368 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 369 consumer.accept(taskInfo.taskId); 370 } 371 } 372 373 /** Collects all the current child tasks and prepares transaction to evict them to display. */ evictAllChildren(WindowContainerTransaction wct)374 void evictAllChildren(WindowContainerTransaction wct) { 375 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children"); 376 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 377 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 378 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 379 } 380 } 381 evictOtherChildren(WindowContainerTransaction wct, int taskId)382 void evictOtherChildren(WindowContainerTransaction wct, int taskId) { 383 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 384 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 385 if (taskId == taskInfo.taskId) continue; 386 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId); 387 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 388 } 389 } 390 evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct)391 void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) { 392 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren"); 393 final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone(); 394 for (int i = 0; i < apps.length; i++) { 395 if (apps[i].mode == MODE_OPENING) { 396 toBeEvict.remove(apps[i].taskId); 397 } 398 } 399 for (int i = toBeEvict.size() - 1; i >= 0; i--) { 400 final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i); 401 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId); 402 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 403 } 404 } 405 evictInvisibleChildren(WindowContainerTransaction wct)406 void evictInvisibleChildren(WindowContainerTransaction wct) { 407 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 408 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 409 if (!taskInfo.isVisible) { 410 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d", 411 taskInfo.taskId); 412 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 413 } 414 } 415 } 416 evictChildren(WindowContainerTransaction wct, int taskId)417 void evictChildren(WindowContainerTransaction wct, int taskId) { 418 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId); 419 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); 420 if (taskInfo != null) { 421 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 422 } 423 } 424 reparentTopTask(WindowContainerTransaction wct)425 void reparentTopTask(WindowContainerTransaction wct) { 426 wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token, 427 CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, 428 true /* onTop */, true /* reparentTopOnly */); 429 } 430 resetBounds(WindowContainerTransaction wct)431 void resetBounds(WindowContainerTransaction wct) { 432 wct.setBounds(mRootTaskInfo.token, null); 433 wct.setAppBounds(mRootTaskInfo.token, null); 434 wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); 435 } 436 onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, @StageType int stage)437 void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, 438 @StageType int stage) { 439 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 440 int taskId = mChildrenTaskInfo.keyAt(i); 441 listener.onTaskStageChanged(taskId, stage, 442 mChildrenTaskInfo.get(taskId).isVisible); 443 } 444 } 445 updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared)446 private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, 447 SurfaceControl leash, boolean firstAppeared) { 448 final Point taskPositionInParent = taskInfo.positionInParent; 449 mSyncQueue.runInSync(t -> { 450 // The task surface might be released before running in the sync queue for the case like 451 // trampoline launch, so check if the surface is valid before processing it. 452 if (!leash.isValid()) { 453 Slog.w(TAG, "Skip updating invalid child task surface of task#" + taskInfo.taskId); 454 return; 455 } 456 t.setCrop(leash, null); 457 t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); 458 if (firstAppeared) { 459 t.setAlpha(leash, 1f); 460 t.setMatrix(leash, 1, 0, 0, 1); 461 t.show(leash); 462 } 463 }); 464 } 465 sendStatusChanged()466 private void sendStatusChanged() { 467 mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); 468 } 469 470 @Override 471 @CallSuper dump(@onNull PrintWriter pw, String prefix)472 public void dump(@NonNull PrintWriter pw, String prefix) { 473 final String innerPrefix = prefix + " "; 474 final String childPrefix = innerPrefix + " "; 475 if (mChildrenTaskInfo.size() > 0) { 476 pw.println(prefix + "Children list:"); 477 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 478 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 479 pw.println(childPrefix + "Task#" + i + " taskID=" + taskInfo.taskId 480 + " baseActivity=" + taskInfo.baseActivity); 481 } 482 } 483 } 484 } 485