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