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