RecentsImpl.java revision f0d1c44a59a10707baa0cca8dd377302260710c1
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 17package com.android.systemui.recents; 18 19import android.app.ActivityManager; 20import android.app.ActivityOptions; 21import android.app.ITaskStackListener; 22import android.content.ActivityNotFoundException; 23import android.content.Context; 24import android.content.Intent; 25import android.content.res.Resources; 26import android.graphics.Bitmap; 27import android.graphics.Canvas; 28import android.graphics.Rect; 29import android.graphics.RectF; 30import android.os.Handler; 31import android.os.SystemClock; 32import android.os.UserHandle; 33import android.util.Log; 34import android.util.MutableBoolean; 35import android.view.AppTransitionAnimationSpec; 36import android.view.LayoutInflater; 37import android.view.View; 38 39import com.android.internal.logging.MetricsLogger; 40import com.android.systemui.Prefs; 41import com.android.systemui.R; 42import com.android.systemui.SystemUIApplication; 43import com.android.systemui.recents.events.EventBus; 44import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; 45import com.android.systemui.recents.events.activity.HideRecentsEvent; 46import com.android.systemui.recents.events.activity.IterateRecentsEvent; 47import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 48import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 49import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 50import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 51import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 52import com.android.systemui.recents.misc.DozeTrigger; 53import com.android.systemui.recents.misc.ForegroundThread; 54import com.android.systemui.recents.misc.SystemServicesProxy; 55import com.android.systemui.recents.model.RecentsTaskLoadPlan; 56import com.android.systemui.recents.model.RecentsTaskLoader; 57import com.android.systemui.recents.model.Task; 58import com.android.systemui.recents.model.TaskGrouping; 59import com.android.systemui.recents.model.TaskStack; 60import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; 61import com.android.systemui.recents.views.TaskStackView; 62import com.android.systemui.recents.views.TaskViewHeader; 63import com.android.systemui.recents.views.TaskViewTransform; 64import com.android.systemui.statusbar.phone.PhoneStatusBar; 65 66import java.util.ArrayList; 67 68import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 69 70/** 71 * An implementation of the Recents component for the current user. For secondary users, this can 72 * be called remotely from the system user. 73 */ 74public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements 75 ActivityOptions.OnAnimationFinishedListener { 76 77 private final static String TAG = "RecentsImpl"; 78 private final static boolean DEBUG = false; 79 80 // The minimum amount of time between each recents button press that we will handle 81 private final static int MIN_TOGGLE_DELAY_MS = 350; 82 // The duration within which the user releasing the alt tab (from when they pressed alt tab) 83 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this 84 // duration, then we will toggle recents after this duration. 85 private final static int FAST_ALT_TAB_DELAY_MS = 225; 86 87 public final static String RECENTS_PACKAGE = "com.android.systemui"; 88 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 89 90 /** 91 * An implementation of ITaskStackListener, that allows us to listen for changes to the system 92 * task stacks and update recents accordingly. 93 */ 94 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable { 95 Handler mHandler; 96 97 public TaskStackListenerImpl(Handler handler) { 98 mHandler = handler; 99 } 100 101 @Override 102 public void onTaskStackChanged() { 103 // Debounce any task stack changes 104 mHandler.removeCallbacks(this); 105 mHandler.post(this); 106 } 107 108 /** Preloads the next task */ 109 public void run() { 110 // TODO: Temporarily skip this if multi stack is enabled 111 /* 112 RecentsConfiguration config = RecentsConfiguration.getInstance(); 113 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 114 RecentsTaskLoader loader = Recents.getTaskLoader(); 115 SystemServicesProxy ssp = Recents.getSystemServices(); 116 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask(); 117 118 // Load the next task only if we aren't svelte 119 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 120 loader.preloadTasks(plan, true); 121 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 122 // This callback is made when a new activity is launched and the old one is paused 123 // so ignore the current activity and try and preload the thumbnail for the 124 // previous one. 125 if (runningTaskInfo != null) { 126 launchOpts.runningTaskId = runningTaskInfo.id; 127 } 128 launchOpts.numVisibleTasks = 2; 129 launchOpts.numVisibleTaskThumbnails = 2; 130 launchOpts.onlyLoadForCache = true; 131 launchOpts.onlyLoadPausedActivities = true; 132 loader.loadTasks(mContext, plan, launchOpts); 133 } 134 */ 135 } 136 } 137 138 private static RecentsTaskLoadPlan sInstanceLoadPlan; 139 140 Context mContext; 141 Handler mHandler; 142 TaskStackListenerImpl mTaskStackListener; 143 RecentsAppWidgetHost mAppWidgetHost; 144 boolean mBootCompleted; 145 boolean mCanReuseTaskStackViews = true; 146 boolean mDraggingInRecents; 147 boolean mReloadTasks; 148 149 // Task launching 150 Rect mSearchBarBounds = new Rect(); 151 Rect mTaskStackBounds = new Rect(); 152 Rect mLastTaskViewBounds = new Rect(); 153 TaskViewTransform mTmpTransform = new TaskViewTransform(); 154 int mStatusBarHeight; 155 int mNavBarHeight; 156 int mNavBarWidth; 157 int mTaskBarHeight; 158 159 // Header (for transition) 160 TaskViewHeader mHeaderBar; 161 final Object mHeaderBarLock = new Object(); 162 TaskStackView mDummyStackView; 163 164 // Variables to keep track of if we need to start recents after binding 165 boolean mTriggeredFromAltTab; 166 long mLastToggleTime; 167 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() { 168 @Override 169 public void run() { 170 // When this fires, then the user has not released alt-tab for at least 171 // FAST_ALT_TAB_DELAY_MS milliseconds 172 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */, 173 false /* reloadTasks */); 174 } 175 }); 176 177 Bitmap mThumbnailTransitionBitmapCache; 178 Task mThumbnailTransitionBitmapCacheKey; 179 180 public RecentsImpl(Context context) { 181 mContext = context; 182 mHandler = new Handler(); 183 mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID); 184 Resources res = mContext.getResources(); 185 LayoutInflater inflater = LayoutInflater.from(mContext); 186 187 // Initialize the static foreground thread 188 ForegroundThread.get(); 189 190 // Register the task stack listener 191 mTaskStackListener = new TaskStackListenerImpl(mHandler); 192 SystemServicesProxy ssp = Recents.getSystemServices(); 193 ssp.registerTaskStackListener(mTaskStackListener); 194 195 // Initialize the static configuration resources 196 mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 197 mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); 198 mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); 199 mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); 200 mDummyStackView = new TaskStackView(mContext, new TaskStack()); 201 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 202 null, false); 203 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */); 204 205 // When we start, preload the data associated with the previous recent tasks. 206 // We can use a new plan since the caches will be the same. 207 RecentsTaskLoader loader = Recents.getTaskLoader(); 208 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 209 loader.preloadTasks(plan, true /* isTopTaskHome */); 210 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 211 launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); 212 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 213 launchOpts.onlyLoadForCache = true; 214 loader.loadTasks(mContext, plan, launchOpts); 215 } 216 217 public void onBootCompleted() { 218 mBootCompleted = true; 219 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */); 220 } 221 222 @Override 223 public void onConfigurationChanged() { 224 // Don't reuse task stack views if the configuration changes 225 mCanReuseTaskStackViews = false; 226 Recents.getConfiguration().updateOnConfigurationChange(); 227 } 228 229 /** 230 * This is only called from the system user's Recents. Secondary users will instead proxy their 231 * visibility change events through to the system user via 232 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 233 */ 234 public void onVisibilityChanged(Context context, boolean visible) { 235 SystemUIApplication app = (SystemUIApplication) context; 236 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 237 if (statusBar != null) { 238 statusBar.updateRecentsVisibility(visible); 239 } 240 } 241 242 /** 243 * This is only called from the system user's Recents. Secondary users will instead proxy their 244 * visibility change events through to the system user via 245 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 246 */ 247 public void onStartScreenPinning(Context context) { 248 SystemUIApplication app = (SystemUIApplication) context; 249 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 250 if (statusBar != null) { 251 statusBar.showScreenPinningRequest(false); 252 } 253 } 254 255 @Override 256 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, 257 boolean animate, boolean reloadTasks) { 258 mTriggeredFromAltTab = triggeredFromAltTab; 259 mDraggingInRecents = draggingInRecents; 260 mReloadTasks = reloadTasks; 261 if (mFastAltTabTrigger.hasTriggered()) { 262 // We are calling this from the doze trigger, so just fall through to show Recents 263 mFastAltTabTrigger.resetTrigger(); 264 } else if (mFastAltTabTrigger.isDozing()) { 265 // We are dozing but haven't yet triggered, ignore this if this is not another alt-tab, 266 // otherwise, this is an additional tab (alt-tab*), which means that we should trigger 267 // immediately (fall through and disable the pending trigger) 268 // TODO: This is tricky, we need to handle the tab key, but Recents has not yet started 269 // so we may actually additional signal to handle multiple quick tab cases. The 270 // severity of this is inversely proportional to the FAST_ALT_TAB_DELAY_MS 271 // duration though 272 if (!triggeredFromAltTab) { 273 return; 274 } 275 mFastAltTabTrigger.stopDozing(); 276 } else { 277 // Otherwise, the doze trigger is not running, and if this is an alt tab, we should 278 // start the trigger and then wait for the hide (or for it to elapse) 279 if (triggeredFromAltTab) { 280 mFastAltTabTrigger.startDozing(); 281 return; 282 } 283 } 284 285 try { 286 // Check if the top task is in the home stack, and start the recents activity 287 SystemServicesProxy ssp = Recents.getSystemServices(); 288 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 289 MutableBoolean isTopTaskHome = new MutableBoolean(true); 290 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 291 startRecentsActivity(topTask, isTopTaskHome.value, animate); 292 } 293 } catch (ActivityNotFoundException e) { 294 Log.e(TAG, "Failed to launch RecentsActivity", e); 295 } 296 } 297 298 @Override 299 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 300 if (mBootCompleted) { 301 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) { 302 // The user has released alt-tab before the trigger has run, so just show the next 303 // task immediately 304 showNextTask(); 305 306 // Cancel the fast alt-tab trigger 307 mFastAltTabTrigger.stopDozing(); 308 mFastAltTabTrigger.resetTrigger(); 309 return; 310 } 311 312 // Defer to the activity to handle hiding recents, if it handles it, then it must still 313 // be visible 314 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab, 315 triggeredFromHomeKey)); 316 } 317 } 318 319 @Override 320 public void toggleRecents() { 321 // Skip this toggle if we are already waiting to trigger recents via alt-tab 322 if (mFastAltTabTrigger.isDozing()) { 323 return; 324 } 325 326 mDraggingInRecents = false; 327 mTriggeredFromAltTab = false; 328 329 try { 330 SystemServicesProxy ssp = Recents.getSystemServices(); 331 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 332 MutableBoolean isTopTaskHome = new MutableBoolean(true); 333 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 334 RecentsConfiguration config = Recents.getConfiguration(); 335 RecentsActivityLaunchState launchState = config.getLaunchState(); 336 RecentsDebugFlags flags = Recents.getDebugFlags(); 337 if (flags.isPageOnToggleEnabled() && !launchState.launchedWithAltTab) { 338 // Notify recents to move onto the next task 339 EventBus.getDefault().post(new IterateRecentsEvent()); 340 } else { 341 // If the user has toggled it too quickly, then just eat up the event here (it's 342 // better than showing a janky screenshot). 343 // NOTE: Ideally, the screenshot mechanism would take the window transform into 344 // account 345 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < MIN_TOGGLE_DELAY_MS) { 346 return; 347 } 348 349 EventBus.getDefault().post(new ToggleRecentsEvent()); 350 mLastToggleTime = SystemClock.elapsedRealtime(); 351 } 352 return; 353 } else { 354 // If the user has toggled it too quickly, then just eat up the event here (it's 355 // better than showing a janky screenshot). 356 // NOTE: Ideally, the screenshot mechanism would take the window transform into 357 // account 358 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < MIN_TOGGLE_DELAY_MS) { 359 return; 360 } 361 362 // Otherwise, start the recents activity 363 startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */); 364 mLastToggleTime = SystemClock.elapsedRealtime(); 365 } 366 } catch (ActivityNotFoundException e) { 367 Log.e(TAG, "Failed to launch RecentsActivity", e); 368 } 369 } 370 371 @Override 372 public void preloadRecents() { 373 // Preload only the raw task list into a new load plan (which will be consumed by the 374 // RecentsActivity) only if there is a task to animate to. 375 SystemServicesProxy ssp = Recents.getSystemServices(); 376 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 377 MutableBoolean topTaskHome = new MutableBoolean(true); 378 RecentsTaskLoader loader = Recents.getTaskLoader(); 379 sInstanceLoadPlan = loader.createLoadPlan(mContext); 380 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) { 381 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); 382 loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value); 383 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 384 if (stack.getStackTaskCount() > 0) { 385 // We try and draw the thumbnail transition bitmap in parallel before 386 // toggle/show recents is called 387 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView); 388 } 389 } 390 } 391 392 @Override 393 public void cancelPreloadingRecents() { 394 // Do nothing 395 } 396 397 @Override 398 public void onDraggingInRecents(float distanceFromTop) { 399 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop)); 400 } 401 402 @Override 403 public void onDraggingInRecentsEnded(float velocity) { 404 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity)); 405 } 406 407 /** 408 * Transitions to the next recent task in the stack. 409 */ 410 public void showNextTask() { 411 SystemServicesProxy ssp = Recents.getSystemServices(); 412 RecentsTaskLoader loader = Recents.getTaskLoader(); 413 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 414 loader.preloadTasks(plan, true /* isTopTaskHome */); 415 TaskStack focusedStack = plan.getTaskStack(); 416 417 // Return early if there are no tasks in the focused stack 418 if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return; 419 420 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask(); 421 // Return early if there is no running task 422 if (runningTask == null) return; 423 424 // Find the task in the recents list 425 boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId); 426 ArrayList<Task> tasks = focusedStack.getStackTasks(); 427 Task toTask = null; 428 ActivityOptions launchOpts = null; 429 int taskCount = tasks.size(); 430 for (int i = taskCount - 1; i >= 1; i--) { 431 Task task = tasks.get(i); 432 if (isTopTaskHome) { 433 toTask = tasks.get(i - 1); 434 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 435 R.anim.recents_launch_next_affiliated_task_target, 436 R.anim.recents_fast_toggle_app_home_exit); 437 break; 438 } else if (task.key.id == runningTask.id) { 439 toTask = tasks.get(i - 1); 440 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 441 R.anim.recents_launch_prev_affiliated_task_target, 442 R.anim.recents_launch_prev_affiliated_task_source); 443 break; 444 } 445 } 446 447 // Return early if there is no next task 448 if (toTask == null) { 449 ssp.startInPlaceAnimationOnFrontMostApplication( 450 ActivityOptions.makeCustomInPlaceAnimation(mContext, 451 R.anim.recents_launch_prev_affiliated_task_bounce)); 452 return; 453 } 454 455 // Launch the task 456 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts); 457 } 458 459 /** 460 * Transitions to the next affiliated task. 461 */ 462 public void showRelativeAffiliatedTask(boolean showNextTask) { 463 SystemServicesProxy ssp = Recents.getSystemServices(); 464 RecentsTaskLoader loader = Recents.getTaskLoader(); 465 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 466 loader.preloadTasks(plan, true /* isTopTaskHome */); 467 TaskStack focusedStack = plan.getTaskStack(); 468 469 // Return early if there are no tasks in the focused stack 470 if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return; 471 472 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask(); 473 // Return early if there is no running task (can't determine affiliated tasks in this case) 474 if (runningTask == null) return; 475 // Return early if the running task is in the home stack (optimization) 476 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return; 477 478 // Find the task in the recents list 479 ArrayList<Task> tasks = focusedStack.getStackTasks(); 480 Task toTask = null; 481 ActivityOptions launchOpts = null; 482 int taskCount = tasks.size(); 483 int numAffiliatedTasks = 0; 484 for (int i = 0; i < taskCount; i++) { 485 Task task = tasks.get(i); 486 if (task.key.id == runningTask.id) { 487 TaskGrouping group = task.group; 488 Task.TaskKey toTaskKey; 489 if (showNextTask) { 490 toTaskKey = group.getNextTaskInGroup(task); 491 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 492 R.anim.recents_launch_next_affiliated_task_target, 493 R.anim.recents_launch_next_affiliated_task_source); 494 } else { 495 toTaskKey = group.getPrevTaskInGroup(task); 496 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 497 R.anim.recents_launch_prev_affiliated_task_target, 498 R.anim.recents_launch_prev_affiliated_task_source); 499 } 500 if (toTaskKey != null) { 501 toTask = focusedStack.findTaskWithId(toTaskKey.id); 502 } 503 numAffiliatedTasks = group.getTaskCount(); 504 break; 505 } 506 } 507 508 // Return early if there is no next task 509 if (toTask == null) { 510 if (numAffiliatedTasks > 1) { 511 if (showNextTask) { 512 ssp.startInPlaceAnimationOnFrontMostApplication( 513 ActivityOptions.makeCustomInPlaceAnimation(mContext, 514 R.anim.recents_launch_next_affiliated_task_bounce)); 515 } else { 516 ssp.startInPlaceAnimationOnFrontMostApplication( 517 ActivityOptions.makeCustomInPlaceAnimation(mContext, 518 R.anim.recents_launch_prev_affiliated_task_bounce)); 519 } 520 } 521 return; 522 } 523 524 // Keep track of actually launched affiliated tasks 525 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 526 527 // Launch the task 528 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts); 529 } 530 531 public void showNextAffiliatedTask() { 532 // Keep track of when the affiliated task is triggered 533 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1); 534 showRelativeAffiliatedTask(true); 535 } 536 537 public void showPrevAffiliatedTask() { 538 // Keep track of when the affiliated task is triggered 539 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1); 540 showRelativeAffiliatedTask(false); 541 } 542 543 public void dockTopTask(boolean draggingInRecents, Rect initialBounds) { 544 SystemServicesProxy ssp = Recents.getSystemServices(); 545 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 546 if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) { 547 ssp.moveTaskToDockedStack(topTask.id, 548 ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, initialBounds); 549 showRecents(false /* triggeredFromAltTab */, draggingInRecents, false /* animate */, 550 true /* reloadTasks*/); 551 } 552 } 553 554 /** 555 * Returns the preloaded load plan and invalidates it. 556 */ 557 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 558 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 559 sInstanceLoadPlan = null; 560 return plan; 561 } 562 563 /** 564 * Prepares the header bar layout for the next transition, if the task view bounds has changed 565 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 566 * 567 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one 568 * is not already bound (can be expensive) 569 * @param stack the stack to initialize the stack layout with 570 */ 571 private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) { 572 RecentsConfiguration config = Recents.getConfiguration(); 573 SystemServicesProxy ssp = Recents.getSystemServices(); 574 Rect windowRect = ssp.getWindowRect(); 575 576 // Update the configuration for the current state 577 config.update(mContext, ssp, ssp.getWindowRect()); 578 579 if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) { 580 // Try and pre-emptively bind the search widget on startup to ensure that we 581 // have the right thumbnail bounds to animate to. 582 // Note: We have to reload the widget id before we get the task stack bounds below 583 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 584 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); 585 } 586 } 587 Rect systemInsets = new Rect(0, mStatusBarHeight, 588 (config.hasTransposedNavBar ? mNavBarWidth : 0), 589 (config.hasTransposedNavBar ? 0 : mNavBarHeight)); 590 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, 591 mSearchBarBounds, mTaskStackBounds); 592 593 // Rebind the header bar and draw it for the transition 594 TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); 595 Rect taskStackBounds = new Rect(mTaskStackBounds); 596 algo.setSystemInsets(systemInsets); 597 if (stack != null) { 598 algo.initialize(taskStackBounds, 599 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); 600 } 601 Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); 602 if (!taskViewBounds.equals(mLastTaskViewBounds)) { 603 mLastTaskViewBounds.set(taskViewBounds); 604 605 int taskViewWidth = taskViewBounds.width(); 606 synchronized (mHeaderBarLock) { 607 mHeaderBar.measure( 608 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), 609 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); 610 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 611 } 612 } 613 } 614 615 /** 616 * Preloads the icon of a task. 617 */ 618 private void preloadIcon(ActivityManager.RunningTaskInfo task) { 619 // Ensure that we load the running task's icon 620 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 621 launchOpts.runningTaskId = task.id; 622 launchOpts.loadThumbnails = false; 623 launchOpts.onlyLoadForCache = true; 624 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 625 } 626 627 /** 628 * Caches the header thumbnail used for a window animation asynchronously into 629 * {@link #mThumbnailTransitionBitmapCache}. 630 */ 631 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 632 TaskStack stack, TaskStackView stackView) { 633 preloadIcon(topTask); 634 635 // Update the header bar if necessary 636 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */, stack); 637 638 // Update the destination rect 639 mDummyStackView.updateLayoutForStack(stack); 640 final Task toTask = new Task(); 641 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 642 topTask.id, toTask); 643 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() { 644 @Override 645 public void run() { 646 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); 647 mHandler.post(new Runnable() { 648 @Override 649 public void run() { 650 mThumbnailTransitionBitmapCache = transitionBitmap; 651 mThumbnailTransitionBitmapCacheKey = toTask; 652 } 653 }); 654 } 655 }); 656 } 657 658 /** 659 * Creates the activity options for a unknown state->recents transition. 660 */ 661 private ActivityOptions getUnknownTransitionActivityOptions() { 662 return ActivityOptions.makeCustomAnimation(mContext, 663 R.anim.recents_from_unknown_enter, 664 R.anim.recents_from_unknown_exit, 665 mHandler, null); 666 } 667 668 /** 669 * Creates the activity options for a home->recents transition. 670 */ 671 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 672 if (fromSearchHome) { 673 return ActivityOptions.makeCustomAnimation(mContext, 674 R.anim.recents_from_search_launcher_enter, 675 R.anim.recents_from_search_launcher_exit, 676 mHandler, null); 677 } 678 return ActivityOptions.makeCustomAnimation(mContext, 679 R.anim.recents_from_launcher_enter, 680 R.anim.recents_from_launcher_exit, 681 mHandler, null); 682 } 683 684 /** 685 * Creates the activity options for an app->recents transition. 686 */ 687 private ActivityOptions getThumbnailTransitionActivityOptions( 688 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { 689 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { 690 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); 691 stackView.getScroller().setStackScrollToInitialState(); 692 ArrayList<Task> tasks = stack.getStackTasks(); 693 for (int i = tasks.size() - 1; i >= 0; i--) { 694 Task task = tasks.get(i); 695 if (task.isFreeformTask()) { 696 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 697 stackView.getScroller().getStackScroll(), mTmpTransform, null); 698 Rect toTaskRect = new Rect(); 699 mTmpTransform.rect.round(toTaskRect); 700 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); 701 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); 702 } 703 } 704 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; 705 specs.toArray(specsArray); 706 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 707 specsArray, mHandler, null, this); 708 } else { 709 // Update the destination rect 710 Task toTask = new Task(); 711 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 712 topTask.id, toTask); 713 RectF toTaskRect = toTransform.rect; 714 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); 715 if (thumbnail != null) { 716 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 717 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, 718 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null); 719 } 720 // If both the screenshot and thumbnail fails, then just fall back to the default transition 721 return getUnknownTransitionActivityOptions(); 722 } 723 } 724 725 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, 726 TaskViewTransform toTransform) { 727 Bitmap thumbnail; 728 if (mThumbnailTransitionBitmapCacheKey != null 729 && mThumbnailTransitionBitmapCacheKey.key != null 730 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 731 thumbnail = mThumbnailTransitionBitmapCache; 732 mThumbnailTransitionBitmapCacheKey = null; 733 mThumbnailTransitionBitmapCache = null; 734 } else { 735 preloadIcon(topTask); 736 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 737 } 738 return thumbnail; 739 } 740 741 /** 742 * Returns the transition rect for the given task id. 743 */ 744 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, 745 TaskStackView stackView, int runningTaskId, Task runningTaskOut) { 746 // Find the running task in the TaskStack 747 Task task = null; 748 ArrayList<Task> tasks = stack.getStackTasks(); 749 if (runningTaskId != -1) { 750 // Otherwise, try and find the task with the 751 int taskCount = tasks.size(); 752 for (int i = taskCount - 1; i >= 0; i--) { 753 Task t = tasks.get(i); 754 if (t.key.id == runningTaskId) { 755 task = t; 756 runningTaskOut.copyFrom(t); 757 break; 758 } 759 } 760 } 761 if (task == null) { 762 // If no task is specified or we can not find the task just use the front most one 763 task = tasks.get(tasks.size() - 1); 764 runningTaskOut.copyFrom(task); 765 } 766 767 // Get the transform for the running task 768 stackView.getScroller().setStackScrollToInitialState(); 769 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 770 stackView.getScroller().getStackScroll(), mTmpTransform, null); 771 return mTmpTransform; 772 } 773 774 /** 775 * Draws the header of a task used for the window animation into a bitmap. 776 */ 777 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 778 if (toTransform != null && toTask.key != null) { 779 Bitmap thumbnail; 780 synchronized (mHeaderBarLock) { 781 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 782 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 783 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 784 Bitmap.Config.ARGB_8888); 785 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { 786 thumbnail.eraseColor(0xFFff0000); 787 } else { 788 Canvas c = new Canvas(thumbnail); 789 c.scale(toTransform.scale, toTransform.scale); 790 mHeaderBar.rebindToTask(toTask); 791 mHeaderBar.draw(c); 792 c.setBitmap(null); 793 } 794 } 795 return thumbnail.createAshmemBitmap(); 796 } 797 return null; 798 } 799 800 /** 801 * Shows the recents activity 802 */ 803 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 804 boolean isTopTaskHome, boolean animate) { 805 RecentsTaskLoader loader = Recents.getTaskLoader(); 806 807 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we 808 // should always preload the tasks now. If we are dragging in recents, reload them as 809 // the stacks might have changed. 810 if (mReloadTasks || mTriggeredFromAltTab ||sInstanceLoadPlan == null) { 811 // Create a new load plan if preloadRecents() was never triggered 812 sInstanceLoadPlan = loader.createLoadPlan(mContext); 813 } 814 if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { 815 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 816 } 817 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 818 819 // Update the header bar if necessary 820 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */, stack); 821 822 // Prepare the dummy stack for the transition 823 mDummyStackView.updateLayoutForStack(stack); 824 TaskStackLayoutAlgorithm.VisibilityReport stackVr = 825 mDummyStackView.computeStackVisibilityReport(); 826 827 if (!animate) { 828 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1); 829 startRecentsActivity(topTask, opts, false /* fromHome */, 830 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr); 831 return; 832 } 833 834 boolean hasRecentTasks = stack.getStackTaskCount() > 0; 835 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 836 837 if (useThumbnailTransition) { 838 // Try starting with a thumbnail transition 839 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 840 mDummyStackView); 841 if (opts != null) { 842 startRecentsActivity(topTask, opts, false /* fromHome */, 843 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 844 } else { 845 // Fall through below to the non-thumbnail transition 846 useThumbnailTransition = false; 847 } 848 } 849 850 if (!useThumbnailTransition) { 851 // If there is no thumbnail transition, but is launching from home into recents, then 852 // use a quick home transition and do the animation from home 853 if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) { 854 SystemServicesProxy ssp = Recents.getSystemServices(); 855 String homeActivityPackage = ssp.getHomeActivityPackageName(); 856 String searchWidgetPackage = Prefs.getString(mContext, 857 Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null); 858 859 // Determine whether we are coming from a search owned home activity 860 boolean fromSearchHome = (homeActivityPackage != null) && 861 homeActivityPackage.equals(searchWidgetPackage); 862 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 863 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 864 false /* fromThumbnail */, stackVr); 865 } else { 866 // Otherwise we do the normal fade from an unknown source 867 ActivityOptions opts = getUnknownTransitionActivityOptions(); 868 startRecentsActivity(topTask, opts, true /* fromHome */, 869 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 870 } 871 } 872 mLastToggleTime = SystemClock.elapsedRealtime(); 873 } 874 875 /** 876 * Starts the recents activity. 877 */ 878 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 879 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, 880 TaskStackLayoutAlgorithm.VisibilityReport vr) { 881 // Update the configuration based on the launch options 882 RecentsConfiguration config = Recents.getConfiguration(); 883 RecentsActivityLaunchState launchState = config.getLaunchState(); 884 launchState.launchedFromHome = fromSearchHome || fromHome; 885 launchState.launchedFromSearchHome = fromSearchHome; 886 launchState.launchedFromAppWithThumbnail = fromThumbnail; 887 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; 888 launchState.launchedWithAltTab = mTriggeredFromAltTab; 889 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews; 890 launchState.launchedNumVisibleTasks = vr.numVisibleTasks; 891 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails; 892 launchState.launchedHasConfigurationChanged = false; 893 launchState.launchedViaDragGesture = mDraggingInRecents; 894 895 Intent intent = new Intent(); 896 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 897 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 898 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 899 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 900 if (opts != null) { 901 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 902 } else { 903 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 904 } 905 mCanReuseTaskStackViews = true; 906 } 907 908 /**** OnAnimationFinishedListener Implementation ****/ 909 910 @Override 911 public void onAnimationFinished() { 912 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent()); 913 } 914} 915