• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.quickstep;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.content.Intent.ACTION_CHOOSER;
22 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
23 import static android.view.Display.DEFAULT_DISPLAY;
24 import static android.view.Display.INVALID_DISPLAY;
25 
26 import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
27 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
28 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_A;
29 import static com.android.quickstep.fallback.window.RecentsWindowFlags.enableOverviewOnConnectedDisplays;
30 import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
31 import static com.android.wm.shell.Flags.enableFlexibleSplit;
32 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
33 import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID;
34 import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
35 
36 import android.app.ActivityManager.RunningTaskInfo;
37 import android.app.TaskInfo;
38 import android.app.WindowConfiguration;
39 import android.content.Context;
40 import android.util.ArrayMap;
41 import android.util.Log;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 import androidx.annotation.UiThread;
46 
47 import com.android.launcher3.dagger.ApplicationContext;
48 import com.android.launcher3.dagger.LauncherAppSingleton;
49 import com.android.launcher3.util.DaggerSingletonObject;
50 import com.android.launcher3.util.DaggerSingletonTracker;
51 import com.android.launcher3.util.SplitConfigurationOptions;
52 import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
53 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
54 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
55 import com.android.launcher3.util.TraceHelper;
56 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
57 import com.android.quickstep.util.DesksUtils;
58 import com.android.quickstep.util.ExternalDisplaysKt;
59 import com.android.systemui.shared.system.ActivityManagerWrapper;
60 import com.android.systemui.shared.system.TaskStackChangeListener;
61 import com.android.systemui.shared.system.TaskStackChangeListeners;
62 import com.android.wm.shell.shared.GroupedTaskInfo;
63 import com.android.wm.shell.splitscreen.ISplitScreenListener;
64 
65 import java.io.PrintWriter;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collections;
69 import java.util.Iterator;
70 import java.util.LinkedList;
71 import java.util.List;
72 
73 import javax.inject.Inject;
74 
75 /**
76  * This class tracked the top-most task and  some 'approximate' task history to allow faster
77  * system state estimation during touch interaction
78  */
79 @LauncherAppSingleton
80 public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
81     private static final String TAG = "TopTaskTracker";
82     public static DaggerSingletonObject<TopTaskTracker> INSTANCE =
83             new DaggerSingletonObject<>(QuickstepBaseAppComponent::getTopTaskTracker);
84 
85     private static final int HISTORY_SIZE = 5;
86 
87     // Only used when Flags.enableShellTopTaskTracking() is disabled
88     // Ordered list with first item being the most recent task.
89     private final LinkedList<TaskInfo> mOrderedTaskList = new LinkedList<>();
90     private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
91     private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
92     private int mPinnedTaskId = INVALID_TASK_ID;
93 
94     // Only used when Flags.enableShellTopTaskTracking() is enabled
95     // Mapping of display id to visible tasks.  Visible tasks are ordered from top most to bottom
96     // most.
97     private ArrayMap<Integer, GroupedTaskInfo> mVisibleTasks = new ArrayMap<>();
98 
99     private final boolean mCanEnterDesktopMode;
100 
101     @Inject
TopTaskTracker(@pplicationContext Context context, DaggerSingletonTracker tracker, SystemUiProxy systemUiProxy)102     public TopTaskTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker,
103             SystemUiProxy systemUiProxy) {
104         if (!enableShellTopTaskTracking()) {
105             mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
106             mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
107 
108             TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
109             systemUiProxy.registerSplitScreenListener(this);
110         }
111 
112         tracker.addCloseable(() -> {
113             if (enableShellTopTaskTracking()) {
114                 return;
115             }
116 
117             TaskStackChangeListeners.getInstance().unregisterTaskStackListener(this);
118             systemUiProxy.unregisterSplitScreenListener(this);
119         });
120 
121         mCanEnterDesktopMode = canEnterDesktopMode(context);
122     }
123 
124     @Override
onTaskRemoved(int taskId)125     public void onTaskRemoved(int taskId) {
126         if (enableShellTopTaskTracking()) {
127             return;
128         }
129 
130         mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
131     }
132 
133     @Override
onTaskMovedToFront(RunningTaskInfo taskInfo)134     public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
135         handleTaskMovedToFront(taskInfo);
136     }
137 
handleTaskMovedToFront(TaskInfo taskInfo)138     void handleTaskMovedToFront(TaskInfo taskInfo) {
139         if (enableShellTopTaskTracking()) {
140             return;
141         }
142 
143         mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
144         mOrderedTaskList.addFirst(taskInfo);
145 
146         // Workaround for b/372067617, if the home task is being brought to front, then it will
147         // occlude all other tasks, so mark them as not-visible
148         if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
149             // We've moved the task to the front of the list above, so only iterate the tasks after
150             for (int i = 1; i < mOrderedTaskList.size(); i++) {
151                 final TaskInfo info = mOrderedTaskList.get(i);
152                 if (info.displayId != taskInfo.displayId) {
153                     // Only fall through to reset visibility for tasks on the same display as the
154                     // home task being brought forward
155                     continue;
156                 }
157                 info.isVisible = false;
158                 info.isVisibleRequested = false;
159             }
160         }
161 
162         // Keep the home display's top running task in the first while adding a non-home
163         // display's task to the list, to avoid showing non-home display's task upon going to
164         // Recents animation.
165         if (taskInfo.displayId != DEFAULT_DISPLAY) {
166             final TaskInfo topTaskOnHomeDisplay = mOrderedTaskList.stream()
167                     .filter(rto -> rto.displayId == DEFAULT_DISPLAY).findFirst().orElse(null);
168             if (topTaskOnHomeDisplay != null) {
169                 mOrderedTaskList.removeIf(rto -> rto.taskId == topTaskOnHomeDisplay.taskId);
170                 mOrderedTaskList.addFirst(topTaskOnHomeDisplay);
171             }
172         }
173 
174         if (mOrderedTaskList.size() >= HISTORY_SIZE) {
175             // If we grow in size, remove the last taskInfo which is not part of the split task.
176             Iterator<TaskInfo> itr = mOrderedTaskList.descendingIterator();
177             while (itr.hasNext()) {
178                 TaskInfo info = itr.next();
179                 if (info.taskId != taskInfo.taskId
180                         && info.taskId != mMainStagePosition.taskId
181                         && info.taskId != mSideStagePosition.taskId) {
182                     itr.remove();
183                     return;
184                 }
185             }
186         }
187     }
188 
189     /**
190      * Called when the set of visible tasks have changed.
191      */
onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks)192     public void onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks) {
193         if (!enableShellTopTaskTracking()) {
194             return;
195         }
196 
197         // Clear existing tasks for each display
198         mVisibleTasks.clear();
199 
200         // Update the visible tasks on each display
201         Log.d(TAG, "onVisibleTasksChanged:");
202         for (GroupedTaskInfo groupedTask : visibleTasks) {
203             Log.d(TAG, "\t" + groupedTask);
204             final int displayId = groupedTask.getBaseGroupedTask().getTaskInfo1().getDisplayId();
205             mVisibleTasks.put(displayId, groupedTask);
206         }
207     }
208 
209     @Override
onStagePositionChanged(@tageType int stage, @StagePosition int position)210     public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
211         if (enableShellTopTaskTracking()) {
212             return;
213         }
214 
215         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
216             mMainStagePosition.stagePosition = position;
217         } else {
218             mSideStagePosition.stagePosition = position;
219         }
220     }
221 
onTaskChanged(RunningTaskInfo taskInfo)222     public void onTaskChanged(RunningTaskInfo taskInfo) {
223         if (enableShellTopTaskTracking()) {
224             return;
225         }
226 
227         for (int i = 0; i < mOrderedTaskList.size(); i++) {
228             if (mOrderedTaskList.get(i).taskId == taskInfo.taskId) {
229                 mOrderedTaskList.set(i, taskInfo);
230                 break;
231             }
232         }
233     }
234 
235     @Override
onTaskStageChanged(int taskId, @StageType int stage, boolean visible)236     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
237         if (enableShellTopTaskTracking()) {
238             return;
239         }
240 
241         // If a task is not visible anymore or has been moved to undefined, stop tracking it.
242         if (!visible || stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
243             if (mMainStagePosition.taskId == taskId) {
244                 mMainStagePosition.taskId = INVALID_TASK_ID;
245             } else if (mSideStagePosition.taskId == taskId) {
246                 mSideStagePosition.taskId = INVALID_TASK_ID;
247             } // else it's an un-tracked child
248             return;
249         }
250 
251         if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN
252                 || (enableFlexibleSplit() && stage == STAGE_TYPE_A)) {
253             mMainStagePosition.taskId = taskId;
254         } else {
255             mSideStagePosition.taskId = taskId;
256         }
257     }
258 
259     @Override
onActivityPinned(String packageName, int userId, int taskId, int stackId)260     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
261         if (enableShellTopTaskTracking()) {
262             return;
263         }
264 
265         mPinnedTaskId = taskId;
266     }
267 
268     @Override
onActivityUnpinned()269     public void onActivityUnpinned() {
270         if (enableShellTopTaskTracking()) {
271             return;
272         }
273 
274         mPinnedTaskId = INVALID_TASK_ID;
275     }
276 
277     /**
278      * Return the running split task ids.  Index 0 will be task in left/top position, index 1 in
279      * right/bottom position, or and empty array if device is not in splitscreen.
280      */
getRunningSplitTaskIds()281     public int[] getRunningSplitTaskIds() {
282         if (enableShellTopTaskTracking()) {
283             // TODO(346588978): This assumes default display as splitscreen is only currently there
284             final GroupedTaskInfo visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
285             final GroupedTaskInfo splitTaskInfo =
286                     visibleTasks != null && visibleTasks.isBaseType(TYPE_SPLIT)
287                             ? visibleTasks.getBaseGroupedTask()
288                             : null;
289             if (splitTaskInfo != null && splitTaskInfo.getSplitBounds() != null) {
290                 return new int[] {
291                         splitTaskInfo.getSplitBounds().leftTopTaskId,
292                         splitTaskInfo.getSplitBounds().rightBottomTaskId
293                 };
294             }
295             return new int[0];
296         } else {
297             if (mMainStagePosition.taskId == INVALID_TASK_ID
298                     || mSideStagePosition.taskId == INVALID_TASK_ID) {
299                 return new int[]{};
300             }
301             int[] out = new int[2];
302             if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
303                 out[0] = mMainStagePosition.taskId;
304                 out[1] = mSideStagePosition.taskId;
305             } else {
306                 out[1] = mMainStagePosition.taskId;
307                 out[0] = mSideStagePosition.taskId;
308             }
309             return out;
310         }
311     }
312 
313     /**
314      * Dumps the list of tasks in top task tracker.
315      */
dump(PrintWriter pw)316     public void dump(PrintWriter pw) {
317         if (!enableShellTopTaskTracking()) {
318             return;
319         }
320 
321         pw.println("TopTaskTracker:");
322         mVisibleTasks.forEach((displayId, tasks) ->
323                 pw.println("  visibleTasks(" + displayId + "): " + tasks));
324     }
325 
326     /**
327      * Returns the CachedTaskInfo for the top most task
328      */
329     @NonNull
330     @UiThread
getCachedTopTask(boolean filterOnlyVisibleRecents, int displayId)331     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents, int displayId) {
332         if (enableShellTopTaskTracking()) {
333             // TODO(346588978): Currently ignore filterOnlyVisibleRecents, but perhaps make this an
334             //  explicit filter For things to ignore (ie. PIP/Bubbles/Assistant/etc/so that this is
335             //  explicit)
336             return new CachedTaskInfo(mVisibleTasks.get(displayId));
337         } else {
338             if (filterOnlyVisibleRecents) {
339                 // Since we only know about the top most task, any filtering may not be applied on
340                 // the cache. The second to top task may change while the top task is still the
341                 // same.
342                 TaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
343                         ActivityManagerWrapper.getInstance().getRunningTasks(true));
344                 if (enableOverviewOnConnectedDisplays()) {
345                     return new CachedTaskInfo(Arrays.stream(tasks).filter(
346                             info -> ExternalDisplaysKt.getSafeDisplayId(info)
347                                     == displayId).toList(), mCanEnterDesktopMode, displayId);
348                 } else {
349                     return new CachedTaskInfo(Arrays.asList(tasks), mCanEnterDesktopMode,
350                             displayId);
351                 }
352             }
353 
354             if (mOrderedTaskList.isEmpty()) {
355                 RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
356                         ActivityManagerWrapper.getInstance().getRunningTasks(
357                                 false /* filterOnlyVisibleRecents */));
358                 Collections.addAll(mOrderedTaskList, tasks);
359             }
360 
361             ArrayList<TaskInfo> tasks = new ArrayList<>(mOrderedTaskList);
362             // Strip the pinned task and recents task
363             tasks.removeIf(t -> t.taskId == mPinnedTaskId || isRecentsTask(t)
364                     ||  DesksUtils.isDesktopWallpaperTask(t));
365             if (enableOverviewOnConnectedDisplays()) {
366                 return new CachedTaskInfo(tasks.stream().filter(
367                         info -> ExternalDisplaysKt.getSafeDisplayId(info) == displayId).toList(),
368                         mCanEnterDesktopMode, displayId);
369             } else {
370                 return new CachedTaskInfo(tasks, mCanEnterDesktopMode, displayId);
371             }
372         }
373     }
374 
isHomeTask(TaskInfo task)375     private static boolean isHomeTask(TaskInfo task) {
376         return task != null && task.configuration.windowConfiguration
377                 .getActivityType() == ACTIVITY_TYPE_HOME;
378     }
379 
isRecentsTask(TaskInfo task)380     private static boolean isRecentsTask(TaskInfo task) {
381         return task != null && task.configuration.windowConfiguration
382                 .getActivityType() == ACTIVITY_TYPE_RECENTS;
383     }
384 
385     /**
386      * Class to provide information about a task which can be safely cached and do not change
387      * during the lifecycle of the task.
388      */
389     public static class CachedTaskInfo {
390         // Only used when enableShellTopTaskTracking() is disabled.
391         private int mDisplayId = INVALID_DISPLAY;
392         // Only used when enableShellTopTaskTracking() is disabled
393         @Nullable
394         private final TaskInfo mTopTask;
395         @Nullable
396         public final List<TaskInfo> mAllCachedTasks;
397 
398         // Only used when enableShellTopTaskTracking() is enabled
399         @Nullable
400         private final GroupedTaskInfo mVisibleTasks;
401 
402         private boolean mCanEnterDesktopMode = false;
403 
404         // Only used when enableShellTopTaskTracking() is enabled
CachedTaskInfo(@ullable GroupedTaskInfo visibleTasks)405         CachedTaskInfo(@Nullable GroupedTaskInfo visibleTasks) {
406             mAllCachedTasks = null;
407             mTopTask = null;
408             mVisibleTasks = visibleTasks;
409         }
410 
411         // Only used when enableShellTopTaskTracking() is disabled
CachedTaskInfo(@onNull List<TaskInfo> allCachedTasks, boolean canEnterDesktopMode, int displayId)412         CachedTaskInfo(@NonNull List<TaskInfo> allCachedTasks, boolean canEnterDesktopMode,
413                 int displayId) {
414             mVisibleTasks = null;
415             mAllCachedTasks = allCachedTasks;
416             mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
417             mCanEnterDesktopMode = canEnterDesktopMode;
418             mDisplayId = displayId;
419         }
420 
421         /**
422          * Returns the "base" task that is used the as the representative running task of the set
423          * of tasks initially provided.
424          *
425          * Not for general use, as in other windowing modes (ie. split/desktop) the caller should
426          * not make assumptions about there being a single base task.
427          * TODO(346588978): Try to remove all usage of this if possible
428          */
429         @Nullable
getLegacyBaseTask()430         private TaskInfo getLegacyBaseTask() {
431             if (enableShellTopTaskTracking()) {
432                 return mVisibleTasks != null
433                         ? mVisibleTasks.getBaseGroupedTask().getTaskInfo1()
434                         : null;
435             } else {
436                 return mTopTask;
437             }
438         }
439 
440         /**
441          * Returns the top task id.
442          */
getTaskId()443         public int getTaskId() {
444             if (enableShellTopTaskTracking()) {
445                 // Callers should use topGroupedTaskContainsTask() instead
446                 return INVALID_TASK_ID;
447             } else {
448                 return mTopTask != null ? mTopTask.taskId : INVALID_TASK_ID;
449             }
450         }
451 
452         /**
453          * Returns the top grouped task ids if Flags.enableShellTopTaskTracking() is true, otherwise
454          * an empty array.
455          */
topGroupedTaskIds()456         public int[] topGroupedTaskIds() {
457             if (enableShellTopTaskTracking()) {
458                 if (mVisibleTasks == null) {
459                     return new int[0];
460                 }
461                 List<TaskInfo> groupedTasks = mVisibleTasks.getTaskInfoList();
462                 return groupedTasks.stream().mapToInt(
463                         groupedTask -> groupedTask.taskId).toArray();
464             } else {
465                 // Not used
466                 return new int[0];
467             }
468         }
469 
470         /**
471          * Returns whether the top grouped task contains the given {@param taskId} if
472          * Flags.enableShellTopTaskTracking() is true, otherwise it checks the top task as reported
473          * from TaskStackListener.
474          */
topGroupedTaskContainsTask(int taskId)475         public boolean topGroupedTaskContainsTask(int taskId) {
476             if (enableShellTopTaskTracking()) {
477                 return mVisibleTasks != null && mVisibleTasks.containsTask(taskId);
478             } else {
479                 return mTopTask != null && mTopTask.taskId == taskId;
480             }
481         }
482 
483         /**
484          * Returns true if this represents the task chooser activity
485          */
isRootChooseActivity()486         public boolean isRootChooseActivity() {
487             final TaskInfo baseTask = getLegacyBaseTask();
488             return baseTask != null && ACTION_CHOOSER.equals(baseTask.baseIntent.getAction());
489         }
490 
491         /**
492          * Returns true if this represents the HOME activity type task
493          */
isHomeTask()494         public boolean isHomeTask() {
495             final TaskInfo baseTask = getLegacyBaseTask();
496             return baseTask != null && TopTaskTracker.isHomeTask(baseTask);
497         }
498 
499         /**
500          * Returns true if this represents the RECENTS activity type task
501          */
isRecentsTask()502         public boolean isRecentsTask() {
503             final TaskInfo baseTask = getLegacyBaseTask();
504             return baseTask != null && TopTaskTracker.isRecentsTask(baseTask);
505         }
506 
507         /**
508          * If the given task holds an activity that is excluded from recents, and there
509          * is another running task that is not excluded from recents, returns that underlying task.
510          */
getVisibleNonExcludedTask()511         public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
512             if (enableShellTopTaskTracking()) {
513                 // Callers should not need this when the full set of visible tasks are provided
514                 return null;
515             }
516             if (mTopTask == null
517                     || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
518                 // Not an excluded task.
519                 return null;
520             }
521             List<TaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
522                     .filter(t -> t.isVisible
523                             && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0
524                             && t.getActivityType() != ACTIVITY_TYPE_HOME
525                             && t.getActivityType() != ACTIVITY_TYPE_RECENTS)
526                     .toList();
527             return visibleNonExcludedTasks.isEmpty() ? null
528                     : new CachedTaskInfo(visibleNonExcludedTasks, mCanEnterDesktopMode, mDisplayId);
529         }
530 
531         /**
532          * Returns {@link TaskInfo} array corresponding to the provided task ids which can be
533          * used as a placeholder until the true object is loaded by the model. Only used when
534          * enableShellTopTaskTracking() is disabled.
535          */
getSplitPlaceholderTasksInfo(int[] splitTaskIds)536         private TaskInfo[] getSplitPlaceholderTasksInfo(int[] splitTaskIds) {
537             if (mTopTask == null) {
538                 return new TaskInfo[0];
539             }
540             TaskInfo[] result = new TaskInfo[splitTaskIds.length];
541             for (int i = 0; i < splitTaskIds.length; i++) {
542                 final int index = i;
543                 int taskId = splitTaskIds[i];
544                 mAllCachedTasks.forEach(rti -> {
545                     if (rti.taskId == taskId) {
546                         result[index] = rti;
547                     }
548                 });
549             }
550             return result;
551         }
552 
isDesktopTask(TaskInfo taskInfo)553         private boolean isDesktopTask(TaskInfo taskInfo) {
554             return mCanEnterDesktopMode
555                     && taskInfo.configuration.windowConfiguration.getWindowingMode()
556                     == WindowConfiguration.WINDOWING_MODE_FREEFORM;
557         }
558 
559         // TODO(346588978): Update this to return more than a single task once the callers
560         //  are refactored.
561         /**
562          * Returns a {@link GroupedTaskInfo} which can be used as a placeholder until the true
563          * object is loaded by the model.
564          *
565          * @param splitTaskIds provide if it is for split, which represents the task ids of the
566          *                     paired tasks. Otherwise, provide null.
567          */
getPlaceholderGroupedTaskInfo(@ullable int[] splitTaskIds)568         public GroupedTaskInfo getPlaceholderGroupedTaskInfo(@Nullable int[] splitTaskIds) {
569             if (enableShellTopTaskTracking()) {
570                 if (mVisibleTasks == null) {
571                     return null;
572                 }
573                 return mVisibleTasks.getBaseGroupedTask();
574             } else {
575                 final TaskInfo baseTaskInfo = getLegacyBaseTask();
576                 if (baseTaskInfo == null) {
577                     return null;
578                 }
579                 if (splitTaskIds != null && splitTaskIds.length >= 2) {
580                     TaskInfo[] splitTasksInfo = getSplitPlaceholderTasksInfo(splitTaskIds);
581                     if (splitTasksInfo[0] == null || splitTasksInfo[1] == null) {
582                         return null;
583                     }
584                     return GroupedTaskInfo.forSplitTasks(splitTasksInfo[0],
585                             splitTasksInfo[1], /* splitBounds = */ null);
586                 } else if (isDesktopTask(baseTaskInfo)) {
587                     return GroupedTaskInfo.forDeskTasks(INACTIVE_DESK_ID, mDisplayId,
588                             Collections.singletonList(
589                                     baseTaskInfo), /* minimizedFreeformTaskIds = */
590                             Collections.emptySet());
591                 } else {
592                     return GroupedTaskInfo.forFullscreenTasks(baseTaskInfo);
593                 }
594             }
595         }
596 
597         @Nullable
getPackageName()598         public String getPackageName() {
599             final TaskInfo baseTask = getLegacyBaseTask();
600             if (baseTask == null || baseTask.baseActivity == null) {
601                 return null;
602             }
603             return baseTask.baseActivity.getPackageName();
604         }
605     }
606 }
607