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