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