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