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