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