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