RecentsImpl.java revision 435b2e43cb131d0b38ba19f335aec78bb83944b2
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 */); 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 */); 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.getTaskCount() > 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.getTaskCount() == 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.getTasks(); 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.getTaskCount() == 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.getTasks(); 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 */ 570 private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) { 571 RecentsConfiguration config = Recents.getConfiguration(); 572 SystemServicesProxy ssp = Recents.getSystemServices(); 573 Rect windowRect = ssp.getWindowRect(); 574 575 // Update the configuration for the current state 576 config.update(mContext, ssp, ssp.getWindowRect()); 577 578 if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) { 579 // Try and pre-emptively bind the search widget on startup to ensure that we 580 // have the right thumbnail bounds to animate to. 581 // Note: We have to reload the widget id before we get the task stack bounds below 582 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 583 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); 584 } 585 } 586 Rect systemInsets = new Rect(0, mStatusBarHeight, 587 (config.hasTransposedNavBar ? mNavBarWidth : 0), 588 (config.hasTransposedNavBar ? 0 : mNavBarHeight)); 589 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, 590 mSearchBarBounds, mTaskStackBounds); 591 592 // Rebind the header bar and draw it for the transition 593 TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); 594 Rect taskStackBounds = new Rect(mTaskStackBounds); 595 algo.setSystemInsets(systemInsets); 596 algo.initialize(taskStackBounds); 597 Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); 598 if (!taskViewBounds.equals(mLastTaskViewBounds)) { 599 mLastTaskViewBounds.set(taskViewBounds); 600 601 int taskViewWidth = taskViewBounds.width(); 602 synchronized (mHeaderBarLock) { 603 mHeaderBar.measure( 604 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), 605 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); 606 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 607 } 608 } 609 } 610 611 /** 612 * Preloads the icon of a task. 613 */ 614 private void preloadIcon(ActivityManager.RunningTaskInfo task) { 615 // Ensure that we load the running task's icon 616 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 617 launchOpts.runningTaskId = task.id; 618 launchOpts.loadThumbnails = false; 619 launchOpts.onlyLoadForCache = true; 620 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 621 } 622 623 /** 624 * Caches the header thumbnail used for a window animation asynchronously into 625 * {@link #mThumbnailTransitionBitmapCache}. 626 */ 627 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 628 TaskStack stack, TaskStackView stackView) { 629 preloadIcon(topTask); 630 631 // Update the header bar if necessary 632 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 633 634 // Update the destination rect 635 mDummyStackView.updateLayoutForStack(stack); 636 final Task toTask = new Task(); 637 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 638 topTask.id, toTask); 639 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() { 640 @Override 641 public void run() { 642 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); 643 mHandler.post(new Runnable() { 644 @Override 645 public void run() { 646 mThumbnailTransitionBitmapCache = transitionBitmap; 647 mThumbnailTransitionBitmapCacheKey = toTask; 648 } 649 }); 650 } 651 }); 652 } 653 654 /** 655 * Creates the activity options for a unknown state->recents transition. 656 */ 657 private ActivityOptions getUnknownTransitionActivityOptions() { 658 return ActivityOptions.makeCustomAnimation(mContext, 659 R.anim.recents_from_unknown_enter, 660 R.anim.recents_from_unknown_exit, 661 mHandler, null); 662 } 663 664 /** 665 * Creates the activity options for a home->recents transition. 666 */ 667 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 668 if (fromSearchHome) { 669 return ActivityOptions.makeCustomAnimation(mContext, 670 R.anim.recents_from_search_launcher_enter, 671 R.anim.recents_from_search_launcher_exit, 672 mHandler, null); 673 } 674 return ActivityOptions.makeCustomAnimation(mContext, 675 R.anim.recents_from_launcher_enter, 676 R.anim.recents_from_launcher_exit, 677 mHandler, null); 678 } 679 680 /** 681 * Creates the activity options for an app->recents transition. 682 */ 683 private ActivityOptions getThumbnailTransitionActivityOptions( 684 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { 685 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { 686 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); 687 stackView.getScroller().setStackScrollToInitialState(); 688 ArrayList<Task> tasks = stack.getTasks(); 689 for (int i = tasks.size() - 1; i >= 0; i--) { 690 Task task = tasks.get(i); 691 if (SystemServicesProxy.isFreeformStack(task.key.stackId)) { 692 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 693 stackView.getScroller().getStackScroll(), mTmpTransform, null); 694 Rect toTaskRect = new Rect(); 695 mTmpTransform.rect.round(toTaskRect); 696 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); 697 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); 698 } 699 } 700 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; 701 specs.toArray(specsArray); 702 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 703 specsArray, mHandler, null, this); 704 } else { 705 // Update the destination rect 706 Task toTask = new Task(); 707 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 708 topTask.id, toTask); 709 RectF toTaskRect = toTransform.rect; 710 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); 711 if (thumbnail != null) { 712 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 713 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, 714 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null); 715 } 716 // If both the screenshot and thumbnail fails, then just fall back to the default transition 717 return getUnknownTransitionActivityOptions(); 718 } 719 } 720 721 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, 722 TaskViewTransform toTransform) { 723 Bitmap thumbnail; 724 if (mThumbnailTransitionBitmapCacheKey != null 725 && mThumbnailTransitionBitmapCacheKey.key != null 726 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 727 thumbnail = mThumbnailTransitionBitmapCache; 728 mThumbnailTransitionBitmapCacheKey = null; 729 mThumbnailTransitionBitmapCache = null; 730 } else { 731 preloadIcon(topTask); 732 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 733 } 734 return thumbnail; 735 } 736 737 /** 738 * Returns the transition rect for the given task id. 739 */ 740 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, 741 TaskStackView stackView, int runningTaskId, Task runningTaskOut) { 742 // Find the running task in the TaskStack 743 Task task = null; 744 ArrayList<Task> tasks = stack.getTasks(); 745 if (runningTaskId != -1) { 746 // Otherwise, try and find the task with the 747 int taskCount = tasks.size(); 748 for (int i = taskCount - 1; i >= 0; i--) { 749 Task t = tasks.get(i); 750 if (t.key.id == runningTaskId) { 751 task = t; 752 runningTaskOut.copyFrom(t); 753 break; 754 } 755 } 756 } 757 if (task == null) { 758 // If no task is specified or we can not find the task just use the front most one 759 task = tasks.get(tasks.size() - 1); 760 runningTaskOut.copyFrom(task); 761 } 762 763 // Get the transform for the running task 764 stackView.getScroller().setStackScrollToInitialState(); 765 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 766 stackView.getScroller().getStackScroll(), mTmpTransform, null); 767 return mTmpTransform; 768 } 769 770 /** 771 * Draws the header of a task used for the window animation into a bitmap. 772 */ 773 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 774 if (toTransform != null && toTask.key != null) { 775 Bitmap thumbnail; 776 synchronized (mHeaderBarLock) { 777 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 778 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 779 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 780 Bitmap.Config.ARGB_8888); 781 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { 782 thumbnail.eraseColor(0xFFff0000); 783 } else { 784 Canvas c = new Canvas(thumbnail); 785 c.scale(toTransform.scale, toTransform.scale); 786 mHeaderBar.rebindToTask(toTask); 787 mHeaderBar.draw(c); 788 c.setBitmap(null); 789 } 790 } 791 return thumbnail.createAshmemBitmap(); 792 } 793 return null; 794 } 795 796 /** 797 * Shows the recents activity 798 */ 799 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 800 boolean isTopTaskHome, boolean animate) { 801 RecentsTaskLoader loader = Recents.getTaskLoader(); 802 803 // Update the header bar if necessary 804 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 805 806 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we 807 // should always preload the tasks now. If we are dragging in recents, reload them as 808 // the stacks might have changed. 809 if (mReloadTasks || mTriggeredFromAltTab ||sInstanceLoadPlan == null) { 810 // Create a new load plan if preloadRecents() was never triggered 811 sInstanceLoadPlan = loader.createLoadPlan(mContext); 812 } 813 if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { 814 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 815 } 816 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 817 818 // Prepare the dummy stack for the transition 819 mDummyStackView.updateLayoutForStack(stack); 820 TaskStackLayoutAlgorithm.VisibilityReport stackVr = 821 mDummyStackView.computeStackVisibilityReport(); 822 823 if (!animate) { 824 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1); 825 startRecentsActivity(topTask, opts, false /* fromHome */, 826 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr); 827 return; 828 } 829 830 boolean hasRecentTasks = stack.getTaskCount() > 0; 831 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 832 833 if (useThumbnailTransition) { 834 // Try starting with a thumbnail transition 835 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 836 mDummyStackView); 837 if (opts != null) { 838 startRecentsActivity(topTask, opts, false /* fromHome */, 839 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 840 } else { 841 // Fall through below to the non-thumbnail transition 842 useThumbnailTransition = false; 843 } 844 } 845 846 if (!useThumbnailTransition) { 847 // If there is no thumbnail transition, but is launching from home into recents, then 848 // use a quick home transition and do the animation from home 849 if (!RecentsDebugFlags.Static.DisableSearchBar && hasRecentTasks) { 850 SystemServicesProxy ssp = Recents.getSystemServices(); 851 String homeActivityPackage = ssp.getHomeActivityPackageName(); 852 String searchWidgetPackage = Prefs.getString(mContext, 853 Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null); 854 855 // Determine whether we are coming from a search owned home activity 856 boolean fromSearchHome = (homeActivityPackage != null) && 857 homeActivityPackage.equals(searchWidgetPackage); 858 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 859 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 860 false /* fromThumbnail */, stackVr); 861 } else { 862 // Otherwise we do the normal fade from an unknown source 863 ActivityOptions opts = getUnknownTransitionActivityOptions(); 864 startRecentsActivity(topTask, opts, true /* fromHome */, 865 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 866 } 867 } 868 mLastToggleTime = SystemClock.elapsedRealtime(); 869 } 870 871 /** 872 * Starts the recents activity. 873 */ 874 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 875 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, 876 TaskStackLayoutAlgorithm.VisibilityReport vr) { 877 // Update the configuration based on the launch options 878 RecentsConfiguration config = Recents.getConfiguration(); 879 RecentsActivityLaunchState launchState = config.getLaunchState(); 880 launchState.launchedFromHome = fromSearchHome || fromHome; 881 launchState.launchedFromSearchHome = fromSearchHome; 882 launchState.launchedFromAppWithThumbnail = fromThumbnail; 883 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; 884 launchState.launchedWithAltTab = mTriggeredFromAltTab; 885 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews; 886 launchState.launchedNumVisibleTasks = vr.numVisibleTasks; 887 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails; 888 launchState.launchedHasConfigurationChanged = false; 889 launchState.launchedViaDragGesture = mDraggingInRecents; 890 891 Intent intent = new Intent(); 892 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 893 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 894 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 895 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 896 if (opts != null) { 897 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 898 } else { 899 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 900 } 901 mCanReuseTaskStackViews = true; 902 } 903 904 /**** OnAnimationFinishedListener Implementation ****/ 905 906 @Override 907 public void onAnimationFinished() { 908 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent()); 909 } 910} 911