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