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