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