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