1 /* 2 * Copyright (C) 2015 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.systemui.recents; 18 19 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 20 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 21 import static android.app.ActivityManager.StackId.isHomeOrRecentsStack; 22 import static android.view.View.MeasureSpec; 23 24 import android.app.ActivityManager; 25 import android.app.ActivityManager.TaskSnapshot; 26 import android.app.ActivityOptions; 27 import android.app.ActivityOptions.OnAnimationStartedListener; 28 import android.content.ActivityNotFoundException; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.res.Resources; 32 import android.graphics.GraphicBuffer; 33 import android.graphics.Rect; 34 import android.graphics.RectF; 35 import android.graphics.drawable.Drawable; 36 import android.os.Handler; 37 import android.os.SystemClock; 38 import android.util.ArraySet; 39 import android.util.Log; 40 import android.util.MutableBoolean; 41 import android.util.Pair; 42 import android.view.AppTransitionAnimationSpec; 43 import android.view.LayoutInflater; 44 import android.view.ViewConfiguration; 45 import android.view.WindowManager; 46 47 import android.widget.Toast; 48 49 import com.google.android.collect.Lists; 50 51 import com.android.internal.logging.MetricsLogger; 52 import com.android.internal.policy.DockedDividerUtils; 53 import com.android.systemui.R; 54 import com.android.systemui.SystemUIApplication; 55 import com.android.systemui.recents.events.EventBus; 56 import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 57 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; 58 import com.android.systemui.recents.events.activity.HideRecentsEvent; 59 import com.android.systemui.recents.events.activity.IterateRecentsEvent; 60 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; 61 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 62 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 63 import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 64 import com.android.systemui.recents.events.component.ActivityPinnedEvent; 65 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent; 66 import com.android.systemui.recents.events.component.HidePipMenuEvent; 67 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 68 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 69 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 70 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 71 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; 72 import com.android.systemui.recents.misc.DozeTrigger; 73 import com.android.systemui.recents.misc.ForegroundThread; 74 import com.android.systemui.recents.misc.SystemServicesProxy; 75 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; 76 import com.android.systemui.recents.model.RecentsTaskLoadPlan; 77 import com.android.systemui.recents.model.RecentsTaskLoader; 78 import com.android.systemui.recents.model.Task; 79 import com.android.systemui.recents.model.Task.TaskKey; 80 import com.android.systemui.recents.model.TaskGrouping; 81 import com.android.systemui.recents.model.TaskStack; 82 import com.android.systemui.recents.model.ThumbnailData; 83 import com.android.systemui.recents.views.RecentsTransitionHelper; 84 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; 85 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; 86 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport; 87 import com.android.systemui.recents.views.TaskStackView; 88 import com.android.systemui.recents.views.TaskStackViewScroller; 89 import com.android.systemui.recents.views.TaskViewHeader; 90 import com.android.systemui.recents.views.TaskViewTransform; 91 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; 92 import com.android.systemui.stackdivider.DividerView; 93 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; 94 import com.android.systemui.statusbar.phone.StatusBar; 95 96 import java.util.ArrayList; 97 98 /** 99 * An implementation of the Recents component for the current user. For secondary users, this can 100 * be called remotely from the system user. 101 */ 102 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener { 103 104 private final static String TAG = "RecentsImpl"; 105 106 // The minimum amount of time between each recents button press that we will handle 107 private final static int MIN_TOGGLE_DELAY_MS = 350; 108 109 // The duration within which the user releasing the alt tab (from when they pressed alt tab) 110 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this 111 // duration, then we will toggle recents after this duration. 112 private final static int FAST_ALT_TAB_DELAY_MS = 225; 113 114 private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>(); 115 116 public final static String RECENTS_PACKAGE = "com.android.systemui"; 117 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 118 119 /** 120 * An implementation of TaskStackListener, that allows us to listen for changes to the system 121 * task stacks and update recents accordingly. 122 */ 123 class TaskStackListenerImpl extends TaskStackListener { 124 125 @Override onTaskStackChangedBackground()126 public void onTaskStackChangedBackground() { 127 // Check this is for the right user 128 if (!checkCurrentUserId(mContext, false /* debug */)) { 129 return; 130 } 131 132 // Preloads the next task 133 RecentsConfiguration config = Recents.getConfiguration(); 134 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 135 Rect windowRect = getWindowRect(null /* windowRectOverride */); 136 if (windowRect.isEmpty()) { 137 return; 138 } 139 140 // Load the next task only if we aren't svelte 141 SystemServicesProxy ssp = Recents.getSystemServices(); 142 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask(); 143 RecentsTaskLoader loader = Recents.getTaskLoader(); 144 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 145 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); 146 TaskStack stack = plan.getTaskStack(); 147 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState(); 148 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 149 150 synchronized (mBackgroundLayoutAlgorithm) { 151 // This callback is made when a new activity is launched and the old one is 152 // paused so ignore the current activity and try and preload the thumbnail for 153 // the previous one. 154 updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect); 155 156 // Launched from app is always the worst case (in terms of how many 157 // thumbnails/tasks visible) 158 launchState.launchedFromApp = true; 159 mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState); 160 VisibilityReport visibilityReport = 161 mBackgroundLayoutAlgorithm.computeStackVisibilityReport( 162 stack.getStackTasks()); 163 164 launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1; 165 launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks; 166 launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails; 167 launchOpts.onlyLoadForCache = true; 168 launchOpts.onlyLoadPausedActivities = true; 169 launchOpts.loadThumbnails = true; 170 } 171 loader.loadTasks(mContext, plan, launchOpts); 172 } 173 } 174 175 @Override onActivityPinned(String packageName, int userId, int taskId)176 public void onActivityPinned(String packageName, int userId, int taskId) { 177 // Check this is for the right user 178 if (!checkCurrentUserId(mContext, false /* debug */)) { 179 return; 180 } 181 182 // This time needs to be fetched the same way the last active time is fetched in 183 // {@link TaskRecord#touchActiveTime} 184 Recents.getConfiguration().getLaunchState().launchedFromPipApp = true; 185 Recents.getConfiguration().getLaunchState().launchedWithNextPipApp = false; 186 EventBus.getDefault().send(new ActivityPinnedEvent(taskId)); 187 consumeInstanceLoadPlan(); 188 sLastPipTime = System.currentTimeMillis(); 189 } 190 191 @Override onActivityUnpinned()192 public void onActivityUnpinned() { 193 // Check this is for the right user 194 if (!checkCurrentUserId(mContext, false /* debug */)) { 195 return; 196 } 197 198 EventBus.getDefault().send(new ActivityUnpinnedEvent()); 199 sLastPipTime = -1; 200 } 201 202 @Override onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)203 public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { 204 // Check this is for the right user 205 if (!checkCurrentUserId(mContext, false /* debug */)) { 206 return; 207 } 208 209 EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, 210 ThumbnailData.createFromTaskSnapshot(snapshot))); 211 } 212 } 213 214 protected static RecentsTaskLoadPlan sInstanceLoadPlan; 215 // Stores the last pinned task time 216 protected static long sLastPipTime = -1; 217 // Stores whether we are waiting for a transition to/from recents to start. During this time, 218 // we disallow the user from manually toggling recents until the transition has started. 219 private static boolean mWaitingForTransitionStart = false; 220 // Stores whether or not the user toggled while we were waiting for a transition to/from 221 // recents. In this case, we defer the toggle state until then and apply it immediately after. 222 private static boolean mToggleFollowingTransitionStart = true; 223 224 private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener = 225 new OnAnimationStartedListener() { 226 @Override 227 public void onAnimationStarted() { 228 setWaitingForTransitionStart(false); 229 } 230 }; 231 232 protected Context mContext; 233 protected Handler mHandler; 234 TaskStackListenerImpl mTaskStackListener; 235 boolean mDraggingInRecents; 236 boolean mLaunchedWhileDocking; 237 238 // Task launching 239 Rect mTmpBounds = new Rect(); 240 TaskViewTransform mTmpTransform = new TaskViewTransform(); 241 int mTaskBarHeight; 242 243 // Header (for transition) 244 TaskViewHeader mHeaderBar; 245 final Object mHeaderBarLock = new Object(); 246 private TaskStackView mDummyStackView; 247 private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm; 248 249 // Variables to keep track of if we need to start recents after binding 250 protected boolean mTriggeredFromAltTab; 251 protected long mLastToggleTime; 252 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() { 253 @Override 254 public void run() { 255 // When this fires, then the user has not released alt-tab for at least 256 // FAST_ALT_TAB_DELAY_MS milliseconds 257 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */, 258 false /* reloadTasks */, false /* fromHome */, 259 DividerView.INVALID_RECENTS_GROW_TARGET); 260 } 261 }); 262 RecentsImpl(Context context)263 public RecentsImpl(Context context) { 264 mContext = context; 265 mHandler = new Handler(); 266 mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null); 267 268 // Initialize the static foreground thread 269 ForegroundThread.get(); 270 271 // Register the task stack listener 272 mTaskStackListener = new TaskStackListenerImpl(); 273 SystemServicesProxy ssp = Recents.getSystemServices(); 274 ssp.registerTaskStackListener(mTaskStackListener); 275 276 // Initialize the static configuration resources 277 mDummyStackView = new TaskStackView(mContext); 278 reloadResources(); 279 } 280 onBootCompleted()281 public void onBootCompleted() { 282 // When we start, preload the data associated with the previous recent tasks. 283 // We can use a new plan since the caches will be the same. 284 RecentsTaskLoader loader = Recents.getTaskLoader(); 285 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 286 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); 287 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 288 launchOpts.numVisibleTasks = loader.getIconCacheSize(); 289 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 290 launchOpts.onlyLoadForCache = true; 291 loader.loadTasks(mContext, plan, launchOpts); 292 } 293 onConfigurationChanged()294 public void onConfigurationChanged() { 295 reloadResources(); 296 mDummyStackView.reloadOnConfigurationChange(); 297 synchronized (mBackgroundLayoutAlgorithm) { 298 mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext); 299 } 300 } 301 302 /** 303 * This is only called from the system user's Recents. Secondary users will instead proxy their 304 * visibility change events through to the system user via 305 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 306 */ onVisibilityChanged(Context context, boolean visible)307 public void onVisibilityChanged(Context context, boolean visible) { 308 Recents.getSystemServices().setRecentsVisibility(visible); 309 } 310 311 /** 312 * This is only called from the system user's Recents. Secondary users will instead proxy their 313 * visibility change events through to the system user via 314 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 315 */ onStartScreenPinning(Context context, int taskId)316 public void onStartScreenPinning(Context context, int taskId) { 317 SystemUIApplication app = (SystemUIApplication) context; 318 StatusBar statusBar = app.getComponent(StatusBar.class); 319 if (statusBar != null) { 320 statusBar.showScreenPinningRequest(taskId, false); 321 } 322 } 323 showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate, boolean launchedWhileDockingTask, boolean fromHome, int growTarget)324 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, 325 boolean animate, boolean launchedWhileDockingTask, boolean fromHome, 326 int growTarget) { 327 mTriggeredFromAltTab = triggeredFromAltTab; 328 mDraggingInRecents = draggingInRecents; 329 mLaunchedWhileDocking = launchedWhileDockingTask; 330 if (mFastAltTabTrigger.isAsleep()) { 331 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset 332 mFastAltTabTrigger.stopDozing(); 333 } else if (mFastAltTabTrigger.isDozing()) { 334 // Fast alt-tab duration has not elapsed. If this is triggered by a different 335 // showRecents() call, then ignore that call for now. 336 // TODO: We can not handle quick tabs that happen between the initial showRecents() call 337 // that started the activity and the activity starting up. The severity of this 338 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though. 339 if (!triggeredFromAltTab) { 340 return; 341 } 342 mFastAltTabTrigger.stopDozing(); 343 } else if (triggeredFromAltTab) { 344 // The fast alt-tab detector is not yet running, so start the trigger and wait for the 345 // hideRecents() call, or for the fast alt-tab duration to elapse 346 mFastAltTabTrigger.startDozing(); 347 return; 348 } 349 350 try { 351 // Check if the top task is in the home stack, and start the recents activity 352 SystemServicesProxy ssp = Recents.getSystemServices(); 353 boolean forceVisible = launchedWhileDockingTask || draggingInRecents; 354 MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible); 355 if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) { 356 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); 357 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate, 358 growTarget); 359 } 360 } catch (ActivityNotFoundException e) { 361 Log.e(TAG, "Failed to launch RecentsActivity", e); 362 } 363 } 364 hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)365 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 366 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) { 367 // The user has released alt-tab before the trigger has run, so just show the next 368 // task immediately 369 showNextTask(); 370 371 // Cancel the fast alt-tab trigger 372 mFastAltTabTrigger.stopDozing(); 373 return; 374 } 375 376 // Defer to the activity to handle hiding recents, if it handles it, then it must still 377 // be visible 378 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab, 379 triggeredFromHomeKey)); 380 } 381 toggleRecents(int growTarget)382 public void toggleRecents(int growTarget) { 383 // Skip preloading if the task is locked 384 SystemServicesProxy ssp = Recents.getSystemServices(); 385 if (ssp.isScreenPinningActive()) { 386 return; 387 } 388 389 // Skip this toggle if we are already waiting to trigger recents via alt-tab 390 if (mFastAltTabTrigger.isDozing()) { 391 return; 392 } 393 394 if (mWaitingForTransitionStart) { 395 mToggleFollowingTransitionStart = true; 396 return; 397 } 398 399 mDraggingInRecents = false; 400 mLaunchedWhileDocking = false; 401 mTriggeredFromAltTab = false; 402 403 try { 404 MutableBoolean isHomeStackVisible = new MutableBoolean(true); 405 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime; 406 407 if (ssp.isRecentsActivityVisible(isHomeStackVisible)) { 408 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 409 RecentsConfiguration config = Recents.getConfiguration(); 410 RecentsActivityLaunchState launchState = config.getLaunchState(); 411 if (!launchState.launchedWithAltTab) { 412 // Has the user tapped quickly? 413 boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); 414 if (Recents.getConfiguration().isGridEnabled) { 415 if (isQuickTap) { 416 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 417 } else { 418 EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent()); 419 } 420 } else { 421 if (!debugFlags.isPagingEnabled() || isQuickTap) { 422 // Launch the next focused task 423 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 424 } else { 425 // Notify recents to move onto the next task 426 EventBus.getDefault().post(new IterateRecentsEvent()); 427 } 428 } 429 } else { 430 // If the user has toggled it too quickly, then just eat up the event here (it's 431 // better than showing a janky screenshot). 432 // NOTE: Ideally, the screenshot mechanism would take the window transform into 433 // account 434 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 435 return; 436 } 437 438 EventBus.getDefault().post(new ToggleRecentsEvent()); 439 mLastToggleTime = SystemClock.elapsedRealtime(); 440 } 441 return; 442 } else { 443 // If the user has toggled it too quickly, then just eat up the event here (it's 444 // better than showing a janky screenshot). 445 // NOTE: Ideally, the screenshot mechanism would take the window transform into 446 // account 447 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 448 return; 449 } 450 451 // Otherwise, start the recents activity 452 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); 453 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */, 454 growTarget); 455 456 // Only close the other system windows if we are actually showing recents 457 ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS); 458 mLastToggleTime = SystemClock.elapsedRealtime(); 459 } 460 } catch (ActivityNotFoundException e) { 461 Log.e(TAG, "Failed to launch RecentsActivity", e); 462 } 463 } 464 465 public void preloadRecents() { 466 // Skip preloading if the task is locked 467 SystemServicesProxy ssp = Recents.getSystemServices(); 468 if (ssp.isScreenPinningActive()) { 469 return; 470 } 471 472 // Preload only the raw task list into a new load plan (which will be consumed by the 473 // RecentsActivity) only if there is a task to animate to. Post this to ensure that we 474 // don't block the touch feedback on the nav bar button which triggers this. 475 mHandler.post(() -> { 476 MutableBoolean isHomeStackVisible = new MutableBoolean(true); 477 if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) { 478 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); 479 if (runningTask == null) { 480 return; 481 } 482 483 RecentsTaskLoader loader = Recents.getTaskLoader(); 484 sInstanceLoadPlan = loader.createLoadPlan(mContext); 485 loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value); 486 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 487 if (stack.getTaskCount() > 0) { 488 // Only preload the icon (but not the thumbnail since it may not have been taken 489 // for the pausing activity) 490 preloadIcon(runningTask.id); 491 492 // At this point, we don't know anything about the stack state. So only 493 // calculate the dimensions of the thumbnail that we need for the transition 494 // into Recents, but do not draw it until we construct the activity options when 495 // we start Recents 496 updateHeaderBarLayout(stack, null /* window rect override*/); 497 } 498 } 499 }); 500 } 501 502 public void cancelPreloadingRecents() { 503 // Do nothing 504 } 505 506 public void onDraggingInRecents(float distanceFromTop) { 507 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop)); 508 } 509 510 public void onDraggingInRecentsEnded(float velocity) { 511 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity)); 512 } 513 514 public void onShowCurrentUserToast(int msgResId, int msgLength) { 515 Toast.makeText(mContext, msgResId, msgLength).show(); 516 } 517 518 /** 519 * Transitions to the next recent task in the stack. 520 */ 521 public void showNextTask() { 522 SystemServicesProxy ssp = Recents.getSystemServices(); 523 RecentsTaskLoader loader = Recents.getTaskLoader(); 524 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 525 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); 526 TaskStack focusedStack = plan.getTaskStack(); 527 528 // Return early if there are no tasks in the focused stack 529 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 530 531 // Return early if there is no running task 532 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); 533 if (runningTask == null) return; 534 535 // Find the task in the recents list 536 boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId); 537 ArrayList<Task> tasks = focusedStack.getStackTasks(); 538 Task toTask = null; 539 ActivityOptions launchOpts = null; 540 int taskCount = tasks.size(); 541 for (int i = taskCount - 1; i >= 1; i--) { 542 Task task = tasks.get(i); 543 if (isRunningTaskInHomeStack) { 544 toTask = tasks.get(i - 1); 545 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 546 R.anim.recents_launch_next_affiliated_task_target, 547 R.anim.recents_fast_toggle_app_home_exit); 548 break; 549 } else if (task.key.id == runningTask.id) { 550 toTask = tasks.get(i - 1); 551 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 552 R.anim.recents_launch_prev_affiliated_task_target, 553 R.anim.recents_launch_prev_affiliated_task_source); 554 break; 555 } 556 } 557 558 // Return early if there is no next task 559 if (toTask == null) { 560 ssp.startInPlaceAnimationOnFrontMostApplication( 561 ActivityOptions.makeCustomInPlaceAnimation(mContext, 562 R.anim.recents_launch_prev_affiliated_task_bounce)); 563 return; 564 } 565 566 // Launch the task 567 ssp.startActivityFromRecents( 568 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID, 569 null /* resultListener */); 570 } 571 572 /** 573 * Transitions to the next affiliated task. 574 */ showRelativeAffiliatedTask(boolean showNextTask)575 public void showRelativeAffiliatedTask(boolean showNextTask) { 576 SystemServicesProxy ssp = Recents.getSystemServices(); 577 RecentsTaskLoader loader = Recents.getTaskLoader(); 578 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 579 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */); 580 TaskStack focusedStack = plan.getTaskStack(); 581 582 // Return early if there are no tasks in the focused stack 583 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 584 585 // Return early if there is no running task (can't determine affiliated tasks in this case) 586 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask(); 587 if (runningTask == null) return; 588 // Return early if the running task is in the home/recents stack (optimization) 589 if (isHomeOrRecentsStack(runningTask.stackId)) return; 590 591 // Find the task in the recents list 592 ArrayList<Task> tasks = focusedStack.getStackTasks(); 593 Task toTask = null; 594 ActivityOptions launchOpts = null; 595 int taskCount = tasks.size(); 596 int numAffiliatedTasks = 0; 597 for (int i = 0; i < taskCount; i++) { 598 Task task = tasks.get(i); 599 if (task.key.id == runningTask.id) { 600 TaskGrouping group = task.group; 601 Task.TaskKey toTaskKey; 602 if (showNextTask) { 603 toTaskKey = group.getNextTaskInGroup(task); 604 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 605 R.anim.recents_launch_next_affiliated_task_target, 606 R.anim.recents_launch_next_affiliated_task_source); 607 } else { 608 toTaskKey = group.getPrevTaskInGroup(task); 609 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 610 R.anim.recents_launch_prev_affiliated_task_target, 611 R.anim.recents_launch_prev_affiliated_task_source); 612 } 613 if (toTaskKey != null) { 614 toTask = focusedStack.findTaskWithId(toTaskKey.id); 615 } 616 numAffiliatedTasks = group.getTaskCount(); 617 break; 618 } 619 } 620 621 // Return early if there is no next task 622 if (toTask == null) { 623 if (numAffiliatedTasks > 1) { 624 if (showNextTask) { 625 ssp.startInPlaceAnimationOnFrontMostApplication( 626 ActivityOptions.makeCustomInPlaceAnimation(mContext, 627 R.anim.recents_launch_next_affiliated_task_bounce)); 628 } else { 629 ssp.startInPlaceAnimationOnFrontMostApplication( 630 ActivityOptions.makeCustomInPlaceAnimation(mContext, 631 R.anim.recents_launch_prev_affiliated_task_bounce)); 632 } 633 } 634 return; 635 } 636 637 // Keep track of actually launched affiliated tasks 638 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 639 640 // Launch the task 641 ssp.startActivityFromRecents( 642 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID, 643 null /* resultListener */); 644 } 645 showNextAffiliatedTask()646 public void showNextAffiliatedTask() { 647 // Keep track of when the affiliated task is triggered 648 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1); 649 showRelativeAffiliatedTask(true); 650 } 651 showPrevAffiliatedTask()652 public void showPrevAffiliatedTask() { 653 // Keep track of when the affiliated task is triggered 654 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1); 655 showRelativeAffiliatedTask(false); 656 } 657 dockTopTask(int topTaskId, int dragMode, int stackCreateMode, Rect initialBounds)658 public void dockTopTask(int topTaskId, int dragMode, 659 int stackCreateMode, Rect initialBounds) { 660 SystemServicesProxy ssp = Recents.getSystemServices(); 661 662 // Make sure we inform DividerView before we actually start the activity so we can change 663 // the resize mode already. 664 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) { 665 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)); 666 showRecents( 667 false /* triggeredFromAltTab */, 668 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS, 669 false /* animate */, 670 true /* launchedWhileDockingTask*/, 671 false /* fromHome */, 672 DividerView.INVALID_RECENTS_GROW_TARGET); 673 } 674 } 675 setWaitingForTransitionStart(boolean waitingForTransitionStart)676 public void setWaitingForTransitionStart(boolean waitingForTransitionStart) { 677 if (mWaitingForTransitionStart == waitingForTransitionStart) { 678 return; 679 } 680 681 mWaitingForTransitionStart = waitingForTransitionStart; 682 if (!waitingForTransitionStart && mToggleFollowingTransitionStart) { 683 mHandler.post(() -> toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET)); 684 } 685 mToggleFollowingTransitionStart = false; 686 } 687 688 /** 689 * Returns the preloaded load plan and invalidates it. 690 */ consumeInstanceLoadPlan()691 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 692 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 693 sInstanceLoadPlan = null; 694 return plan; 695 } 696 697 /** 698 * @return the time at which a task last entered picture-in-picture. 699 */ getLastPipTime()700 public static long getLastPipTime() { 701 return sLastPipTime; 702 } 703 704 /** 705 * Clears the time at which a task last entered picture-in-picture. 706 */ clearLastPipTime()707 public static void clearLastPipTime() { 708 sLastPipTime = -1; 709 } 710 711 /** 712 * Reloads all the resources for the current configuration. 713 */ reloadResources()714 private void reloadResources() { 715 Resources res = mContext.getResources(); 716 717 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext, 718 R.dimen.recents_task_view_header_height, 719 R.dimen.recents_task_view_header_height, 720 R.dimen.recents_task_view_header_height, 721 R.dimen.recents_task_view_header_height_tablet_land, 722 R.dimen.recents_task_view_header_height, 723 R.dimen.recents_task_view_header_height_tablet_land, 724 R.dimen.recents_grid_task_view_header_height); 725 726 LayoutInflater inflater = LayoutInflater.from(mContext); 727 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 728 null, false); 729 mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection()); 730 } 731 updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout, TaskStack stack, Rect windowRect)732 private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout, 733 TaskStack stack, Rect windowRect) { 734 SystemServicesProxy ssp = Recents.getSystemServices(); 735 Rect displayRect = ssp.getDisplayRect(); 736 Rect systemInsets = new Rect(); 737 ssp.getStableInsets(systemInsets); 738 739 // When docked, the nav bar insets are consumed and the activity is measured without insets. 740 // However, the window bounds include the insets, so we need to subtract them here to make 741 // them identical. 742 if (ssp.hasDockedTask()) { 743 windowRect.bottom -= systemInsets.bottom; 744 systemInsets.bottom = 0; 745 } 746 calculateWindowStableInsets(systemInsets, windowRect, displayRect); 747 windowRect.offsetTo(0, 0); 748 749 // Rebind the header bar and draw it for the transition 750 stackLayout.setSystemInsets(systemInsets); 751 if (stack != null) { 752 stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top, 753 systemInsets.left, systemInsets.right, mTmpBounds); 754 stackLayout.reset(); 755 stackLayout.initialize(displayRect, windowRect, mTmpBounds, 756 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); 757 } 758 } 759 getWindowRect(Rect windowRectOverride)760 private Rect getWindowRect(Rect windowRectOverride) { 761 return windowRectOverride != null 762 ? new Rect(windowRectOverride) 763 : Recents.getSystemServices().getWindowRect(); 764 } 765 766 /** 767 * Prepares the header bar layout for the next transition, if the task view bounds has changed 768 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 769 * 770 * @param stack the stack to initialize the stack layout with 771 * @param windowRectOverride the rectangle to use when calculating the stack state which can 772 * be different from the current window rect if recents is resizing 773 * while being launched 774 */ updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride)775 private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) { 776 Rect windowRect = getWindowRect(windowRectOverride); 777 int taskViewWidth = 0; 778 boolean useGridLayout = mDummyStackView.useGridLayout(); 779 updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect); 780 if (stack != null) { 781 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); 782 mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */); 783 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */); 784 // Get the width of a task view so that we know how wide to draw the header bar. 785 if (useGridLayout) { 786 TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm(); 787 gridLayout.initialize(windowRect); 788 taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */, 789 stack.getTaskCount(), new TaskViewTransform(), 790 stackLayout).rect.width(); 791 } else { 792 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds(); 793 if (!taskViewBounds.isEmpty()) { 794 taskViewWidth = taskViewBounds.width(); 795 } 796 } 797 } 798 799 if (stack != null && taskViewWidth > 0) { 800 synchronized (mHeaderBarLock) { 801 if (mHeaderBar.getMeasuredWidth() != taskViewWidth || 802 mHeaderBar.getMeasuredHeight() != mTaskBarHeight) { 803 if (useGridLayout) { 804 mHeaderBar.setShouldDarkenBackgroundColor(true); 805 mHeaderBar.setNoUserInteractionState(); 806 } 807 mHeaderBar.forceLayout(); 808 mHeaderBar.measure( 809 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY), 810 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY)); 811 } 812 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 813 } 814 } 815 } 816 817 /** 818 * Given the stable insets and the rect for our window, calculates the insets that affect our 819 * window. 820 */ calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect)821 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) { 822 823 // Display rect without insets - available app space 824 Rect appRect = new Rect(displayRect); 825 appRect.inset(inOutInsets); 826 827 // Our window intersected with available app space 828 Rect windowRectWithInsets = new Rect(windowRect); 829 windowRectWithInsets.intersect(appRect); 830 inOutInsets.left = windowRectWithInsets.left - windowRect.left; 831 inOutInsets.top = windowRectWithInsets.top - windowRect.top; 832 inOutInsets.right = windowRect.right - windowRectWithInsets.right; 833 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom; 834 } 835 836 /** 837 * Preloads the icon of a task. 838 */ preloadIcon(int runningTaskId)839 private void preloadIcon(int runningTaskId) { 840 // Ensure that we load the running task's icon 841 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 842 launchOpts.runningTaskId = runningTaskId; 843 launchOpts.loadThumbnails = false; 844 launchOpts.onlyLoadForCache = true; 845 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 846 } 847 848 /** 849 * Creates the activity options for a unknown state->recents transition. 850 */ getUnknownTransitionActivityOptions()851 protected ActivityOptions getUnknownTransitionActivityOptions() { 852 return ActivityOptions.makeCustomAnimation(mContext, 853 R.anim.recents_from_unknown_enter, 854 R.anim.recents_from_unknown_exit, 855 mHandler, null); 856 } 857 858 /** 859 * Creates the activity options for a home->recents transition. 860 */ getHomeTransitionActivityOptions()861 protected ActivityOptions getHomeTransitionActivityOptions() { 862 return ActivityOptions.makeCustomAnimation(mContext, 863 R.anim.recents_from_launcher_enter, 864 R.anim.recents_from_launcher_exit, 865 mHandler, null); 866 } 867 868 /** 869 * Creates the activity options for an app->recents transition. 870 */ 871 private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect)872 getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, 873 Rect windowOverrideRect) { 874 final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice; 875 if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { 876 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); 877 ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks(); 878 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); 879 TaskStackViewScroller stackScroller = mDummyStackView.getScroller(); 880 881 mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */); 882 mDummyStackView.updateToInitialState(); 883 884 for (int i = tasks.size() - 1; i >= 0; i--) { 885 Task task = tasks.get(i); 886 if (task.isFreeformTask()) { 887 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, 888 stackScroller.getStackScroll(), mTmpTransform, null, 889 windowOverrideRect); 890 GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform); 891 Rect toTaskRect = new Rect(); 892 mTmpTransform.rect.round(toTaskRect); 893 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); 894 } 895 } 896 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; 897 specs.toArray(specsArray); 898 899 // For low end ram devices, wait for transition flag is reset when Recents entrance 900 // animation is complete instead of when the transition animation starts 901 return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 902 specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this), 903 null); 904 } else { 905 // Update the destination rect 906 Task toTask = new Task(); 907 TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, 908 windowOverrideRect); 909 910 RectF toTaskRect = toTransform.rect; 911 AppTransitionAnimationSpecsFuture future = 912 new RecentsTransitionHelper(mContext).getAppTransitionFuture( 913 () -> { 914 Rect rect = new Rect(); 915 toTaskRect.round(rect); 916 GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, 917 toTransform); 918 return Lists.newArrayList(new AppTransitionAnimationSpec( 919 toTask.key.id, thumbnail, rect)); 920 }); 921 922 // For low end ram devices, wait for transition flag is reset when Recents entrance 923 // animation is complete instead of when the transition animation starts 924 return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext, 925 mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener, 926 false /* scaleUp */), future); 927 } 928 } 929 930 /** 931 * Returns the transition rect for the given task id. 932 */ getThumbnailTransitionTransform(TaskStackView stackView, Task runningTaskOut, Rect windowOverrideRect)933 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView, 934 Task runningTaskOut, Rect windowOverrideRect) { 935 // Find the running task in the TaskStack 936 TaskStack stack = stackView.getStack(); 937 Task launchTask = stack.getLaunchTarget(); 938 if (launchTask != null) { 939 runningTaskOut.copyFrom(launchTask); 940 } else { 941 // If no task is specified or we can not find the task just use the front most one 942 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */); 943 runningTaskOut.copyFrom(launchTask); 944 } 945 946 // Get the transform for the running task 947 stackView.updateLayoutAlgorithm(true /* boundScroll */); 948 stackView.updateToInitialState(); 949 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, 950 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect); 951 return mTmpTransform; 952 } 953 954 /** 955 * Draws the header of a task used for the window animation into a bitmap. 956 */ drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform)957 private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask, 958 TaskViewTransform toTransform) { 959 SystemServicesProxy ssp = Recents.getSystemServices(); 960 int width = (int) toTransform.rect.width(); 961 int height = (int) toTransform.rect.height(); 962 if (toTransform != null && toTask.key != null && width > 0 && height > 0) { 963 synchronized (mHeaderBarLock) { 964 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); 965 mHeaderBar.onTaskViewSizeChanged(width, height); 966 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { 967 return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight, 968 null, 1f, 0xFFff0000); 969 } else { 970 // Workaround for b/27815919, reset the callback so that we do not trigger an 971 // invalidate on the header bar as a result of updating the icon 972 Drawable icon = mHeaderBar.getIconView().getDrawable(); 973 if (icon != null) { 974 icon.setCallback(null); 975 } 976 mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */, 977 disabledInSafeMode); 978 mHeaderBar.onTaskDataLoaded(); 979 mHeaderBar.setDimAlpha(toTransform.dimAlpha); 980 return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight, 981 mHeaderBar, 1f, 0); 982 } 983 } 984 } 985 return null; 986 } 987 988 /** 989 * Shows the recents activity 990 */ startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, boolean isHomeStackVisible, boolean animate, int growTarget)991 protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, 992 boolean isHomeStackVisible, boolean animate, int growTarget) { 993 RecentsTaskLoader loader = Recents.getTaskLoader(); 994 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 995 SystemServicesProxy ssp = Recents.getSystemServices(); 996 boolean isBlacklisted = (runningTask != null) 997 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName()) 998 : false; 999 1000 int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null) 1001 ? runningTask.id 1002 : -1; 1003 1004 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we 1005 // should always preload the tasks now. If we are dragging in recents, reload them as 1006 // the stacks might have changed. 1007 if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) { 1008 // Create a new load plan if preloadRecents() was never triggered 1009 sInstanceLoadPlan = loader.createLoadPlan(mContext); 1010 } 1011 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { 1012 loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible); 1013 } 1014 1015 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 1016 boolean hasRecentTasks = stack.getTaskCount() > 0; 1017 boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible && 1018 hasRecentTasks; 1019 1020 // Update the launch state that we need in updateHeaderBarLayout() 1021 launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking; 1022 launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking; 1023 launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted; 1024 launchState.launchedFromPipApp = false; 1025 launchState.launchedWithNextPipApp = 1026 stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime()); 1027 launchState.launchedViaDockGesture = mLaunchedWhileDocking; 1028 launchState.launchedViaDragGesture = mDraggingInRecents; 1029 launchState.launchedToTaskId = runningTaskId; 1030 launchState.launchedWithAltTab = mTriggeredFromAltTab; 1031 1032 // Disable toggling of recents between starting the activity and it is visible and the app 1033 // has started its transition into recents. 1034 setWaitingForTransitionStart(useThumbnailTransition); 1035 1036 // Preload the icon (this will be a null-op if we have preloaded the icon already in 1037 // preloadRecents()) 1038 preloadIcon(runningTaskId); 1039 1040 // Update the header bar if necessary 1041 Rect windowOverrideRect = getWindowRectOverride(growTarget); 1042 updateHeaderBarLayout(stack, windowOverrideRect); 1043 1044 // Prepare the dummy stack for the transition 1045 TaskStackLayoutAlgorithm.VisibilityReport stackVr = 1046 mDummyStackView.computeStackVisibilityReport(); 1047 1048 // Update the remaining launch state 1049 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks; 1050 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails; 1051 1052 if (!animate) { 1053 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1), 1054 null /* future */); 1055 return; 1056 } 1057 1058 Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair; 1059 if (isBlacklisted) { 1060 pair = new Pair<>(getUnknownTransitionActivityOptions(), null); 1061 } else if (useThumbnailTransition) { 1062 // Try starting with a thumbnail transition 1063 pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect); 1064 } else { 1065 // If there is no thumbnail transition, but is launching from home into recents, then 1066 // use a quick home transition 1067 pair = new Pair<>(hasRecentTasks 1068 ? getHomeTransitionActivityOptions() 1069 : getUnknownTransitionActivityOptions(), null); 1070 } 1071 startRecentsActivity(pair.first, pair.second); 1072 mLastToggleTime = SystemClock.elapsedRealtime(); 1073 } 1074 getWindowRectOverride(int growTarget)1075 private Rect getWindowRectOverride(int growTarget) { 1076 if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) { 1077 return SystemServicesProxy.getInstance(mContext).getWindowRect(); 1078 } 1079 Rect result = new Rect(); 1080 Rect displayRect = Recents.getSystemServices().getDisplayRect(); 1081 DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM, 1082 result, displayRect.width(), displayRect.height(), 1083 Recents.getSystemServices().getDockedDividerSize(mContext)); 1084 return result; 1085 } 1086 1087 /** 1088 * Starts the recents activity. 1089 */ startRecentsActivity(ActivityOptions opts, final AppTransitionAnimationSpecsFuture future)1090 private void startRecentsActivity(ActivityOptions opts, 1091 final AppTransitionAnimationSpecsFuture future) { 1092 Intent intent = new Intent(); 1093 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 1094 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1095 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 1096 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 1097 HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent(); 1098 hideMenuEvent.addPostAnimationCallback(() -> { 1099 Recents.getSystemServices().startActivityAsUserAsync(intent, opts); 1100 EventBus.getDefault().send(new RecentsActivityStartingEvent()); 1101 if (future != null) { 1102 future.precacheSpecs(); 1103 } 1104 }); 1105 EventBus.getDefault().send(hideMenuEvent); 1106 } 1107 1108 /**** OnAnimationFinishedListener Implementation ****/ 1109 1110 @Override onAnimationFinished()1111 public void onAnimationFinished() { 1112 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent()); 1113 } 1114 } 1115