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