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