RecentsImpl.java revision 90186c6d6e7589835db183f602fb48e23a759a87
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 public void dockTopTask() { 409 SystemServicesProxy ssp = Recents.getSystemServices(); 410 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 411 if (topTask != null && !ssp.isInHomeStack(topTask.id)) { 412 ssp.startTaskInDockedMode(topTask.id, 413 ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); 414 showRecents(false /* triggeredFromAltTab */); 415 } 416 } 417 418 /** 419 * Returns the preloaded load plan and invalidates it. 420 */ 421 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 422 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 423 sInstanceLoadPlan = null; 424 return plan; 425 } 426 427 /** 428 * Prepares the header bar layout for the next transition, if the task view bounds has changed 429 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 430 * 431 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one 432 * is not already bound (can be expensive) 433 */ 434 private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) { 435 SystemServicesProxy ssp = Recents.getSystemServices(); 436 Rect windowRect = ssp.getWindowRect(); 437 438 // Update the configuration for the current state 439 mConfig.update(mContext, ssp, ssp.getWindowRect()); 440 441 if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) { 442 // Try and pre-emptively bind the search widget on startup to ensure that we 443 // have the right thumbnail bounds to animate to. 444 // Note: We have to reload the widget id before we get the task stack bounds below 445 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 446 mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); 447 } 448 } 449 Rect systemInsets = new Rect(0, mStatusBarHeight, 450 (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), 451 (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight)); 452 mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, 453 mSearchBarBounds, mTaskStackBounds); 454 455 // Rebind the header bar and draw it for the transition 456 TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); 457 Rect taskStackBounds = new Rect(mTaskStackBounds); 458 algo.setSystemInsets(systemInsets); 459 algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds); 460 Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); 461 if (!taskViewBounds.equals(mLastTaskViewBounds)) { 462 mLastTaskViewBounds.set(taskViewBounds); 463 464 int taskViewWidth = taskViewBounds.width(); 465 synchronized (mHeaderBarLock) { 466 mHeaderBar.measure( 467 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), 468 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); 469 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 470 } 471 } 472 } 473 474 /** 475 * Preloads the icon of a task. 476 */ 477 private void preloadIcon(ActivityManager.RunningTaskInfo task) { 478 // Ensure that we load the running task's icon 479 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 480 launchOpts.runningTaskId = task.id; 481 launchOpts.loadThumbnails = false; 482 launchOpts.onlyLoadForCache = true; 483 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 484 } 485 486 /** 487 * Caches the header thumbnail used for a window animation asynchronously into 488 * {@link #mThumbnailTransitionBitmapCache}. 489 */ 490 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 491 TaskStack stack, TaskStackView stackView) { 492 preloadIcon(topTask); 493 494 // Update the header bar if necessary 495 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 496 497 // Update the destination rect 498 mDummyStackView.updateMinMaxScrollForStack(stack); 499 final Task toTask = new Task(); 500 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 501 topTask.id, toTask); 502 ForegroundThread.getHandler().post(new Runnable() { 503 @Override 504 public void run() { 505 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); 506 mHandler.post(new Runnable() { 507 @Override 508 public void run() { 509 mThumbnailTransitionBitmapCache = transitionBitmap; 510 mThumbnailTransitionBitmapCacheKey = toTask; 511 } 512 }); 513 } 514 }); 515 } 516 517 /** 518 * Creates the activity options for a unknown state->recents transition. 519 */ 520 private ActivityOptions getUnknownTransitionActivityOptions() { 521 return ActivityOptions.makeCustomAnimation(mContext, 522 R.anim.recents_from_unknown_enter, 523 R.anim.recents_from_unknown_exit, 524 mHandler, this); 525 } 526 527 /** 528 * Creates the activity options for a home->recents transition. 529 */ 530 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 531 if (fromSearchHome) { 532 return ActivityOptions.makeCustomAnimation(mContext, 533 R.anim.recents_from_search_launcher_enter, 534 R.anim.recents_from_search_launcher_exit, 535 mHandler, this); 536 } 537 return ActivityOptions.makeCustomAnimation(mContext, 538 R.anim.recents_from_launcher_enter, 539 R.anim.recents_from_launcher_exit, 540 mHandler, this); 541 } 542 543 /** 544 * Creates the activity options for an app->recents transition. 545 */ 546 private ActivityOptions getThumbnailTransitionActivityOptions( 547 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { 548 549 // Update the destination rect 550 Task toTask = new Task(); 551 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 552 topTask.id, toTask); 553 RectF toTaskRect = toTransform.rect; 554 Bitmap thumbnail; 555 if (mThumbnailTransitionBitmapCacheKey != null 556 && mThumbnailTransitionBitmapCacheKey.key != null 557 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 558 thumbnail = mThumbnailTransitionBitmapCache; 559 mThumbnailTransitionBitmapCacheKey = null; 560 mThumbnailTransitionBitmapCache = null; 561 } else { 562 preloadIcon(topTask); 563 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 564 } 565 if (thumbnail != null) { 566 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 567 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, 568 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this); 569 } 570 571 // If both the screenshot and thumbnail fails, then just fall back to the default transition 572 return getUnknownTransitionActivityOptions(); 573 } 574 575 /** 576 * Returns the transition rect for the given task id. 577 */ 578 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, 579 TaskStackView stackView, int runningTaskId, Task runningTaskOut) { 580 // Find the running task in the TaskStack 581 Task task = null; 582 ArrayList<Task> tasks = stack.getTasks(); 583 if (runningTaskId != -1) { 584 // Otherwise, try and find the task with the 585 int taskCount = tasks.size(); 586 for (int i = taskCount - 1; i >= 0; i--) { 587 Task t = tasks.get(i); 588 if (t.key.id == runningTaskId) { 589 task = t; 590 runningTaskOut.copyFrom(t); 591 break; 592 } 593 } 594 } 595 if (task == null) { 596 // If no task is specified or we can not find the task just use the front most one 597 task = tasks.get(tasks.size() - 1); 598 runningTaskOut.copyFrom(task); 599 } 600 601 // Get the transform for the running task 602 stackView.getScroller().setStackScrollToInitialState(); 603 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 604 stackView.getScroller().getStackScroll(), mTmpTransform, null); 605 return mTmpTransform; 606 } 607 608 /** 609 * Draws the header of a task used for the window animation into a bitmap. 610 */ 611 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 612 if (toTransform != null && toTask.key != null) { 613 Bitmap thumbnail; 614 synchronized (mHeaderBarLock) { 615 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 616 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 617 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 618 Bitmap.Config.ARGB_8888); 619 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 620 thumbnail.eraseColor(0xFFff0000); 621 } else { 622 Canvas c = new Canvas(thumbnail); 623 c.scale(toTransform.scale, toTransform.scale); 624 mHeaderBar.rebindToTask(toTask); 625 mHeaderBar.draw(c); 626 c.setBitmap(null); 627 } 628 } 629 return thumbnail.createAshmemBitmap(); 630 } 631 return null; 632 } 633 634 /** 635 * Shows the recents activity 636 */ 637 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 638 boolean isTopTaskHome) { 639 SystemServicesProxy ssp = Recents.getSystemServices(); 640 RecentsTaskLoader loader = Recents.getTaskLoader(); 641 642 // Update the header bar if necessary 643 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 644 645 if (sInstanceLoadPlan == null) { 646 // Create a new load plan if onPreloadRecents() was never triggered 647 sInstanceLoadPlan = loader.createLoadPlan(mContext); 648 } 649 650 if (!sInstanceLoadPlan.hasTasks()) { 651 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 652 } 653 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 654 655 // Prepare the dummy stack for the transition 656 mDummyStackView.updateMinMaxScrollForStack(stack); 657 TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = 658 mDummyStackView.computeStackVisibilityReport(); 659 boolean hasRecentTasks = stack.getTaskCount() > 0; 660 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 661 662 if (useThumbnailTransition) { 663 // Try starting with a thumbnail transition 664 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 665 mDummyStackView); 666 if (opts != null) { 667 startRecentsActivity(topTask, opts, false /* fromHome */, 668 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 669 } else { 670 // Fall through below to the non-thumbnail transition 671 useThumbnailTransition = false; 672 } 673 } 674 675 if (!useThumbnailTransition) { 676 // If there is no thumbnail transition, but is launching from home into recents, then 677 // use a quick home transition and do the animation from home 678 if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) { 679 String homeActivityPackage = ssp.getHomeActivityPackageName(); 680 String searchWidgetPackage = Prefs.getString(mContext, 681 Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null); 682 683 // Determine whether we are coming from a search owned home activity 684 boolean fromSearchHome = (homeActivityPackage != null) && 685 homeActivityPackage.equals(searchWidgetPackage); 686 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 687 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 688 false /* fromThumbnail */, stackVr); 689 } else { 690 // Otherwise we do the normal fade from an unknown source 691 ActivityOptions opts = getUnknownTransitionActivityOptions(); 692 startRecentsActivity(topTask, opts, true /* fromHome */, 693 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 694 } 695 } 696 mLastToggleTime = SystemClock.elapsedRealtime(); 697 } 698 699 /** 700 * Starts the recents activity. 701 */ 702 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 703 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, 704 TaskStackViewLayoutAlgorithm.VisibilityReport vr) { 705 mStartAnimationTriggered = false; 706 707 // Update the configuration based on the launch options 708 RecentsActivityLaunchState launchState = mConfig.getLaunchState(); 709 launchState.launchedFromHome = fromSearchHome || fromHome; 710 launchState.launchedFromSearchHome = fromSearchHome; 711 launchState.launchedFromAppWithThumbnail = fromThumbnail; 712 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; 713 launchState.launchedWithAltTab = mTriggeredFromAltTab; 714 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews; 715 launchState.launchedNumVisibleTasks = vr.numVisibleTasks; 716 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails; 717 launchState.launchedHasConfigurationChanged = false; 718 719 Intent intent = new Intent(); 720 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 721 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 722 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 723 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 724 if (opts != null) { 725 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 726 } else { 727 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 728 } 729 mCanReuseTaskStackViews = true; 730 } 731 732 /**** OnAnimationStartedListener Implementation ****/ 733 734 @Override 735 public void onAnimationStarted() { 736 // Notify recents to start the enter animation 737 if (!mStartAnimationTriggered) { 738 mStartAnimationTriggered = true; 739 EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent()); 740 } 741 } 742} 743