RecentsImpl.java revision 6b614433f667a4213969fe208822f0733e8fa6c5
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.FREEFORM_WORKSPACE_STACK_ID; 20 21import android.app.ActivityManager; 22import android.app.ActivityOptions; 23import android.app.ITaskStackListener; 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.os.Handler; 33import android.os.SystemClock; 34import android.os.UserHandle; 35import android.util.MutableBoolean; 36import android.view.AppTransitionAnimationSpec; 37import android.view.LayoutInflater; 38import android.view.View; 39 40import com.android.internal.logging.MetricsLogger; 41import com.android.systemui.Prefs; 42import com.android.systemui.R; 43import com.android.systemui.SystemUIApplication; 44import com.android.systemui.recents.events.EventBus; 45import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent; 46import com.android.systemui.recents.events.activity.HideRecentsEvent; 47import com.android.systemui.recents.events.activity.IterateRecentsEvent; 48import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 49import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 50import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 51import com.android.systemui.recents.misc.Console; 52import com.android.systemui.recents.misc.ForegroundThread; 53import com.android.systemui.recents.misc.SystemServicesProxy; 54import com.android.systemui.recents.model.RecentsTaskLoadPlan; 55import com.android.systemui.recents.model.RecentsTaskLoader; 56import com.android.systemui.recents.model.Task; 57import com.android.systemui.recents.model.TaskGrouping; 58import com.android.systemui.recents.model.TaskStack; 59import com.android.systemui.recents.views.TaskStackView; 60import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm; 61import com.android.systemui.recents.views.TaskViewHeader; 62import com.android.systemui.recents.views.TaskViewTransform; 63import com.android.systemui.statusbar.phone.PhoneStatusBar; 64 65import java.util.ArrayList; 66 67/** 68 * An implementation of the Recents component for the current user. For secondary users, this can 69 * be called remotely from the system user. 70 */ 71public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub 72 implements ActivityOptions.OnAnimationStartedListener { 73 74 private final static String TAG = "RecentsImpl"; 75 private final static boolean DEBUG = false; 76 77 private final static int sMinToggleDelay = 350; 78 79 public final static String RECENTS_PACKAGE = "com.android.systemui"; 80 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 81 82 83 /** 84 * An implementation of ITaskStackListener, that allows us to listen for changes to the system 85 * task stacks and update recents accordingly. 86 */ 87 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable { 88 Handler mHandler; 89 90 public TaskStackListenerImpl(Handler handler) { 91 mHandler = handler; 92 } 93 94 @Override 95 public void onTaskStackChanged() { 96 // Debounce any task stack changes 97 mHandler.removeCallbacks(this); 98 mHandler.post(this); 99 } 100 101 /** Preloads the next task */ 102 public void run() { 103 // TODO: Temporarily skip this if multi stack is enabled 104 /* 105 RecentsConfiguration config = RecentsConfiguration.getInstance(); 106 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 107 RecentsTaskLoader loader = Recents.getTaskLoader(); 108 SystemServicesProxy ssp = Recents.getSystemServices(); 109 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask(); 110 111 // Load the next task only if we aren't svelte 112 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 113 loader.preloadTasks(plan, true); 114 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 115 // This callback is made when a new activity is launched and the old one is paused 116 // so ignore the current activity and try and preload the thumbnail for the 117 // previous one. 118 if (runningTaskInfo != null) { 119 launchOpts.runningTaskId = runningTaskInfo.id; 120 } 121 launchOpts.numVisibleTasks = 2; 122 launchOpts.numVisibleTaskThumbnails = 2; 123 launchOpts.onlyLoadForCache = true; 124 launchOpts.onlyLoadPausedActivities = true; 125 loader.loadTasks(mContext, plan, launchOpts); 126 } 127 */ 128 } 129 } 130 131 private static RecentsTaskLoadPlan sInstanceLoadPlan; 132 133 Context mContext; 134 Handler mHandler; 135 TaskStackListenerImpl mTaskStackListener; 136 RecentsAppWidgetHost mAppWidgetHost; 137 boolean mBootCompleted; 138 boolean mStartAnimationTriggered; 139 boolean mCanReuseTaskStackViews = true; 140 141 // Task launching 142 Rect mSearchBarBounds = new Rect(); 143 Rect mTaskStackBounds = new Rect(); 144 Rect mLastTaskViewBounds = 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 TaskStackView mDummyStackView; 155 156 // Variables to keep track of if we need to start recents after binding 157 boolean mTriggeredFromAltTab; 158 long mLastToggleTime; 159 160 Bitmap mThumbnailTransitionBitmapCache; 161 Task mThumbnailTransitionBitmapCacheKey; 162 163 164 public RecentsImpl(Context context) { 165 mContext = context; 166 mHandler = new Handler(); 167 mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId); 168 Resources res = mContext.getResources(); 169 LayoutInflater inflater = LayoutInflater.from(mContext); 170 171 // Initialize the static foreground thread 172 ForegroundThread.get(); 173 174 // Register the task stack listener 175 mTaskStackListener = new TaskStackListenerImpl(mHandler); 176 SystemServicesProxy ssp = Recents.getSystemServices(); 177 ssp.registerTaskStackListener(mTaskStackListener); 178 179 // Initialize the static configuration resources 180 mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 181 mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); 182 mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); 183 mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); 184 mDummyStackView = new TaskStackView(mContext, new TaskStack()); 185 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 186 null, false); 187 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */); 188 189 // When we start, preload the data associated with the previous recent tasks. 190 // We can use a new plan since the caches will be the same. 191 RecentsTaskLoader loader = Recents.getTaskLoader(); 192 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 193 loader.preloadTasks(plan, true /* isTopTaskHome */); 194 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 195 launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); 196 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 197 launchOpts.onlyLoadForCache = true; 198 loader.loadTasks(mContext, plan, launchOpts); 199 } 200 201 public void onBootCompleted() { 202 mBootCompleted = true; 203 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */); 204 } 205 206 @Override 207 public void onConfigurationChanged() { 208 // Don't reuse task stack views if the configuration changes 209 mCanReuseTaskStackViews = false; 210 Recents.getConfiguration().updateOnConfigurationChange(); 211 } 212 213 /** 214 * This is only called from the system user's Recents. Secondary users will instead proxy their 215 * visibility change events through to the system user via 216 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 217 */ 218 public void onVisibilityChanged(Context context, boolean visible) { 219 SystemUIApplication app = (SystemUIApplication) context; 220 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 221 if (statusBar != null) { 222 statusBar.updateRecentsVisibility(visible); 223 } 224 } 225 226 /** 227 * This is only called from the system user's Recents. Secondary users will instead proxy their 228 * visibility change events through to the system user via 229 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 230 */ 231 public void onStartScreenPinning(Context context) { 232 SystemUIApplication app = (SystemUIApplication) context; 233 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 234 if (statusBar != null) { 235 statusBar.showScreenPinningRequest(false); 236 } 237 } 238 239 @Override 240 public void showRecents(boolean triggeredFromAltTab) { 241 mTriggeredFromAltTab = triggeredFromAltTab; 242 243 try { 244 // Check if the top task is in the home stack, and start the recents activity 245 SystemServicesProxy ssp = Recents.getSystemServices(); 246 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 247 MutableBoolean isTopTaskHome = new MutableBoolean(true); 248 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 249 startRecentsActivity(topTask, isTopTaskHome.value); 250 } 251 } catch (ActivityNotFoundException e) { 252 Console.logRawError("Failed to launch RecentAppsIntent", e); 253 } 254 } 255 256 @Override 257 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 258 if (mBootCompleted) { 259 // Defer to the activity to handle hiding recents, if it handles it, then it must still 260 // be visible 261 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab, 262 triggeredFromHomeKey)); 263 } 264 } 265 266 @Override 267 public void toggleRecents() { 268 mTriggeredFromAltTab = false; 269 270 try { 271 SystemServicesProxy ssp = Recents.getSystemServices(); 272 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 273 MutableBoolean isTopTaskHome = new MutableBoolean(true); 274 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 275 if (Constants.DebugFlags.App.EnableFastToggleRecents) { 276 // Notify recents to move onto the next task 277 EventBus.getDefault().post(new IterateRecentsEvent()); 278 } else { 279 // If the user has toggled it too quickly, then just eat up the event here (it's 280 // better than showing a janky screenshot). 281 // NOTE: Ideally, the screenshot mechanism would take the window transform into 282 // account 283 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { 284 return; 285 } 286 287 EventBus.getDefault().post(new ToggleRecentsEvent()); 288 mLastToggleTime = SystemClock.elapsedRealtime(); 289 } 290 return; 291 } else { 292 // If the user has toggled it too quickly, then just eat up the event here (it's 293 // better than showing a janky screenshot). 294 // NOTE: Ideally, the screenshot mechanism would take the window transform into 295 // account 296 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { 297 return; 298 } 299 300 // Otherwise, start the recents activity 301 startRecentsActivity(topTask, isTopTaskHome.value); 302 mLastToggleTime = SystemClock.elapsedRealtime(); 303 } 304 } catch (ActivityNotFoundException e) { 305 Console.logRawError("Failed to launch RecentAppsIntent", e); 306 } 307 } 308 309 @Override 310 public void preloadRecents() { 311 // Preload only the raw task list into a new load plan (which will be consumed by the 312 // RecentsActivity) only if there is a task to animate to. 313 SystemServicesProxy ssp = Recents.getSystemServices(); 314 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 315 MutableBoolean topTaskHome = new MutableBoolean(true); 316 RecentsTaskLoader loader = Recents.getTaskLoader(); 317 sInstanceLoadPlan = loader.createLoadPlan(mContext); 318 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) { 319 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); 320 loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value); 321 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 322 if (stack.getTaskCount() > 0) { 323 // We try and draw the thumbnail transition bitmap in parallel before 324 // toggle/show recents is called 325 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView); 326 } 327 } 328 } 329 330 @Override 331 public void cancelPreloadingRecents() { 332 // Do nothing 333 } 334 335 public void showRelativeAffiliatedTask(boolean showNextTask) { 336 // Return early if there is no focused stack 337 SystemServicesProxy ssp = Recents.getSystemServices(); 338 int focusedStackId = ssp.getFocusedStack(); 339 RecentsTaskLoader loader = Recents.getTaskLoader(); 340 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 341 loader.preloadTasks(plan, true /* isTopTaskHome */); 342 TaskStack focusedStack = plan.getTaskStack(); 343 344 // Return early if there are no tasks in the focused stack 345 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 346 347 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask(); 348 // Return early if there is no running task (can't determine affiliated tasks in this case) 349 if (runningTask == null) return; 350 // Return early if the running task is in the home stack (optimization) 351 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return; 352 353 // Find the task in the recents list 354 ArrayList<Task> tasks = focusedStack.getTasks(); 355 Task toTask = null; 356 ActivityOptions launchOpts = null; 357 int taskCount = tasks.size(); 358 int numAffiliatedTasks = 0; 359 for (int i = 0; i < taskCount; i++) { 360 Task task = tasks.get(i); 361 if (task.key.id == runningTask.id) { 362 TaskGrouping group = task.group; 363 Task.TaskKey toTaskKey; 364 if (showNextTask) { 365 toTaskKey = group.getNextTaskInGroup(task); 366 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 367 R.anim.recents_launch_next_affiliated_task_target, 368 R.anim.recents_launch_next_affiliated_task_source); 369 } else { 370 toTaskKey = group.getPrevTaskInGroup(task); 371 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 372 R.anim.recents_launch_prev_affiliated_task_target, 373 R.anim.recents_launch_prev_affiliated_task_source); 374 } 375 if (toTaskKey != null) { 376 toTask = focusedStack.findTaskWithId(toTaskKey.id); 377 } 378 numAffiliatedTasks = group.getTaskCount(); 379 break; 380 } 381 } 382 383 // Return early if there is no next task 384 if (toTask == null) { 385 if (numAffiliatedTasks > 1) { 386 if (showNextTask) { 387 ssp.startInPlaceAnimationOnFrontMostApplication( 388 ActivityOptions.makeCustomInPlaceAnimation(mContext, 389 R.anim.recents_launch_next_affiliated_task_bounce)); 390 } else { 391 ssp.startInPlaceAnimationOnFrontMostApplication( 392 ActivityOptions.makeCustomInPlaceAnimation(mContext, 393 R.anim.recents_launch_prev_affiliated_task_bounce)); 394 } 395 } 396 return; 397 } 398 399 // Keep track of actually launched affiliated tasks 400 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 401 402 // Launch the task 403 if (toTask.isActive) { 404 // Bring an active task to the foreground 405 ssp.moveTaskToFront(toTask.key.id, launchOpts); 406 } else { 407 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts); 408 } 409 } 410 411 public void showNextAffiliatedTask() { 412 // Keep track of when the affiliated task is triggered 413 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1); 414 showRelativeAffiliatedTask(true); 415 } 416 417 public void showPrevAffiliatedTask() { 418 // Keep track of when the affiliated task is triggered 419 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1); 420 showRelativeAffiliatedTask(false); 421 } 422 423 public void dockTopTask() { 424 SystemServicesProxy ssp = Recents.getSystemServices(); 425 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 426 if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) { 427 ssp.startTaskInDockedMode(topTask.id, 428 ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); 429 showRecents(false /* triggeredFromAltTab */); 430 } 431 } 432 433 /** 434 * Returns the preloaded load plan and invalidates it. 435 */ 436 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 437 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 438 sInstanceLoadPlan = null; 439 return plan; 440 } 441 442 /** 443 * Prepares the header bar layout for the next transition, if the task view bounds has changed 444 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 445 * 446 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one 447 * is not already bound (can be expensive) 448 */ 449 private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) { 450 RecentsConfiguration config = Recents.getConfiguration(); 451 SystemServicesProxy ssp = Recents.getSystemServices(); 452 Rect windowRect = ssp.getWindowRect(); 453 454 // Update the configuration for the current state 455 config.update(mContext, ssp, ssp.getWindowRect()); 456 457 if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) { 458 // Try and pre-emptively bind the search widget on startup to ensure that we 459 // have the right thumbnail bounds to animate to. 460 // Note: We have to reload the widget id before we get the task stack bounds below 461 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 462 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); 463 } 464 } 465 Rect systemInsets = new Rect(0, mStatusBarHeight, 466 (config.hasTransposedNavBar ? mNavBarWidth : 0), 467 (config.hasTransposedNavBar ? 0 : mNavBarHeight)); 468 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, 469 mSearchBarBounds, mTaskStackBounds); 470 471 // Rebind the header bar and draw it for the transition 472 TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); 473 Rect taskStackBounds = new Rect(mTaskStackBounds); 474 algo.setSystemInsets(systemInsets); 475 algo.computeRects(taskStackBounds); 476 Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); 477 if (!taskViewBounds.equals(mLastTaskViewBounds)) { 478 mLastTaskViewBounds.set(taskViewBounds); 479 480 int taskViewWidth = taskViewBounds.width(); 481 synchronized (mHeaderBarLock) { 482 mHeaderBar.measure( 483 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), 484 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); 485 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 486 } 487 } 488 } 489 490 /** 491 * Preloads the icon of a task. 492 */ 493 private void preloadIcon(ActivityManager.RunningTaskInfo task) { 494 // Ensure that we load the running task's icon 495 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 496 launchOpts.runningTaskId = task.id; 497 launchOpts.loadThumbnails = false; 498 launchOpts.onlyLoadForCache = true; 499 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 500 } 501 502 /** 503 * Caches the header thumbnail used for a window animation asynchronously into 504 * {@link #mThumbnailTransitionBitmapCache}. 505 */ 506 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 507 TaskStack stack, TaskStackView stackView) { 508 preloadIcon(topTask); 509 510 // Update the header bar if necessary 511 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 512 513 // Update the destination rect 514 mDummyStackView.updateMinMaxScrollForStack(stack); 515 final Task toTask = new Task(); 516 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 517 topTask.id, toTask); 518 ForegroundThread.getHandler().post(new Runnable() { 519 @Override 520 public void run() { 521 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); 522 mHandler.post(new Runnable() { 523 @Override 524 public void run() { 525 mThumbnailTransitionBitmapCache = transitionBitmap; 526 mThumbnailTransitionBitmapCacheKey = toTask; 527 } 528 }); 529 } 530 }); 531 } 532 533 /** 534 * Creates the activity options for a unknown state->recents transition. 535 */ 536 private ActivityOptions getUnknownTransitionActivityOptions() { 537 return ActivityOptions.makeCustomAnimation(mContext, 538 R.anim.recents_from_unknown_enter, 539 R.anim.recents_from_unknown_exit, 540 mHandler, this); 541 } 542 543 /** 544 * Creates the activity options for a home->recents transition. 545 */ 546 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 547 if (fromSearchHome) { 548 return ActivityOptions.makeCustomAnimation(mContext, 549 R.anim.recents_from_search_launcher_enter, 550 R.anim.recents_from_search_launcher_exit, 551 mHandler, this); 552 } 553 return ActivityOptions.makeCustomAnimation(mContext, 554 R.anim.recents_from_launcher_enter, 555 R.anim.recents_from_launcher_exit, 556 mHandler, this); 557 } 558 559 /** 560 * Creates the activity options for an app->recents transition. 561 */ 562 private ActivityOptions getThumbnailTransitionActivityOptions( 563 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { 564 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { 565 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); 566 stackView.getScroller().setStackScrollToInitialState(); 567 ArrayList<Task> tasks = stack.getTasks(); 568 for (int i = tasks.size() - 1; i >= 0; i--) { 569 Task task = tasks.get(i); 570 if (SystemServicesProxy.isFreeformStack(task.key.stackId)) { 571 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 572 stackView.getScroller().getStackScroll(), mTmpTransform, null); 573 Rect toTaskRect = new Rect(); 574 mTmpTransform.rect.round(toTaskRect); 575 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); 576 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); 577 } 578 } 579 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; 580 specs.toArray(specsArray); 581 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 582 specsArray, mHandler, this); 583 } else { 584 // Update the destination rect 585 Task toTask = new Task(); 586 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 587 topTask.id, toTask); 588 RectF toTaskRect = toTransform.rect; 589 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); 590 if (thumbnail != null) { 591 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 592 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, 593 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this); 594 } 595 // If both the screenshot and thumbnail fails, then just fall back to the default transition 596 return getUnknownTransitionActivityOptions(); 597 } 598 } 599 600 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, 601 TaskViewTransform toTransform) { 602 Bitmap thumbnail; 603 if (mThumbnailTransitionBitmapCacheKey != null 604 && mThumbnailTransitionBitmapCacheKey.key != null 605 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 606 thumbnail = mThumbnailTransitionBitmapCache; 607 mThumbnailTransitionBitmapCacheKey = null; 608 mThumbnailTransitionBitmapCache = null; 609 } else { 610 preloadIcon(topTask); 611 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 612 } 613 return thumbnail; 614 } 615 616 /** 617 * Returns the transition rect for the given task id. 618 */ 619 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, 620 TaskStackView stackView, int runningTaskId, Task runningTaskOut) { 621 // Find the running task in the TaskStack 622 Task task = null; 623 ArrayList<Task> tasks = stack.getTasks(); 624 if (runningTaskId != -1) { 625 // Otherwise, try and find the task with the 626 int taskCount = tasks.size(); 627 for (int i = taskCount - 1; i >= 0; i--) { 628 Task t = tasks.get(i); 629 if (t.key.id == runningTaskId) { 630 task = t; 631 runningTaskOut.copyFrom(t); 632 break; 633 } 634 } 635 } 636 if (task == null) { 637 // If no task is specified or we can not find the task just use the front most one 638 task = tasks.get(tasks.size() - 1); 639 runningTaskOut.copyFrom(task); 640 } 641 642 // Get the transform for the running task 643 stackView.getScroller().setStackScrollToInitialState(); 644 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 645 stackView.getScroller().getStackScroll(), mTmpTransform, null); 646 return mTmpTransform; 647 } 648 649 /** 650 * Draws the header of a task used for the window animation into a bitmap. 651 */ 652 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 653 if (toTransform != null && toTask.key != null) { 654 Bitmap thumbnail; 655 synchronized (mHeaderBarLock) { 656 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 657 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 658 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 659 Bitmap.Config.ARGB_8888); 660 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 661 thumbnail.eraseColor(0xFFff0000); 662 } else { 663 Canvas c = new Canvas(thumbnail); 664 c.scale(toTransform.scale, toTransform.scale); 665 mHeaderBar.rebindToTask(toTask); 666 mHeaderBar.draw(c); 667 c.setBitmap(null); 668 } 669 } 670 return thumbnail.createAshmemBitmap(); 671 } 672 return null; 673 } 674 675 /** 676 * Shows the recents activity 677 */ 678 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 679 boolean isTopTaskHome) { 680 SystemServicesProxy ssp = Recents.getSystemServices(); 681 RecentsTaskLoader loader = Recents.getTaskLoader(); 682 683 // Update the header bar if necessary 684 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 685 686 if (sInstanceLoadPlan == null) { 687 // Create a new load plan if onPreloadRecents() was never triggered 688 sInstanceLoadPlan = loader.createLoadPlan(mContext); 689 } 690 691 if (!sInstanceLoadPlan.hasTasks()) { 692 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 693 } 694 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 695 696 // Prepare the dummy stack for the transition 697 mDummyStackView.updateMinMaxScrollForStack(stack); 698 TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = 699 mDummyStackView.computeStackVisibilityReport(); 700 boolean hasRecentTasks = stack.getTaskCount() > 0; 701 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 702 703 if (useThumbnailTransition) { 704 // Try starting with a thumbnail transition 705 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 706 mDummyStackView); 707 if (opts != null) { 708 startRecentsActivity(topTask, opts, false /* fromHome */, 709 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 710 } else { 711 // Fall through below to the non-thumbnail transition 712 useThumbnailTransition = false; 713 } 714 } 715 716 if (!useThumbnailTransition) { 717 // If there is no thumbnail transition, but is launching from home into recents, then 718 // use a quick home transition and do the animation from home 719 if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) { 720 String homeActivityPackage = ssp.getHomeActivityPackageName(); 721 String searchWidgetPackage = Prefs.getString(mContext, 722 Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null); 723 724 // Determine whether we are coming from a search owned home activity 725 boolean fromSearchHome = (homeActivityPackage != null) && 726 homeActivityPackage.equals(searchWidgetPackage); 727 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 728 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 729 false /* fromThumbnail */, stackVr); 730 } else { 731 // Otherwise we do the normal fade from an unknown source 732 ActivityOptions opts = getUnknownTransitionActivityOptions(); 733 startRecentsActivity(topTask, opts, true /* fromHome */, 734 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 735 } 736 } 737 mLastToggleTime = SystemClock.elapsedRealtime(); 738 } 739 740 /** 741 * Starts the recents activity. 742 */ 743 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 744 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, 745 TaskStackViewLayoutAlgorithm.VisibilityReport vr) { 746 mStartAnimationTriggered = false; 747 748 // Update the configuration based on the launch options 749 RecentsConfiguration config = Recents.getConfiguration(); 750 RecentsActivityLaunchState launchState = config.getLaunchState(); 751 launchState.launchedFromHome = fromSearchHome || fromHome; 752 launchState.launchedFromSearchHome = fromSearchHome; 753 launchState.launchedFromAppWithThumbnail = fromThumbnail; 754 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; 755 launchState.launchedWithAltTab = mTriggeredFromAltTab; 756 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews; 757 launchState.launchedNumVisibleTasks = vr.numVisibleTasks; 758 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails; 759 launchState.launchedHasConfigurationChanged = false; 760 761 Intent intent = new Intent(); 762 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 763 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 764 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 765 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 766 if (opts != null) { 767 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 768 } else { 769 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 770 } 771 mCanReuseTaskStackViews = true; 772 } 773 774 /**** OnAnimationStartedListener Implementation ****/ 775 776 @Override 777 public void onAnimationStarted() { 778 // Notify recents to start the enter animation 779 if (!mStartAnimationTriggered) { 780 mStartAnimationTriggered = true; 781 EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent()); 782 } 783 } 784} 785