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