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