RecentsImpl.java revision 190fe3bf88388fcb109af64571e3baa0d01f1c37
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.Activity; 20import android.app.ActivityManager; 21import android.app.ActivityOptions; 22import android.app.ITaskStackListener; 23import android.content.ActivityNotFoundException; 24import android.content.BroadcastReceiver; 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.os.AsyncTask; 32import android.os.Handler; 33import android.os.SystemClock; 34import android.os.UserHandle; 35import android.util.MutableBoolean; 36import android.view.LayoutInflater; 37import android.view.View; 38import com.android.internal.logging.MetricsLogger; 39import com.android.systemui.Prefs; 40import com.android.systemui.R; 41import com.android.systemui.SystemUIApplication; 42import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 43import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 44import com.android.systemui.recents.misc.Console; 45import com.android.systemui.recents.misc.SystemServicesProxy; 46import com.android.systemui.recents.model.RecentsTaskLoadPlan; 47import com.android.systemui.recents.model.RecentsTaskLoader; 48import com.android.systemui.recents.model.Task; 49import com.android.systemui.recents.model.TaskGrouping; 50import com.android.systemui.recents.model.TaskStack; 51import com.android.systemui.recents.views.TaskStackView; 52import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm; 53import com.android.systemui.recents.views.TaskViewHeader; 54import com.android.systemui.recents.views.TaskViewTransform; 55import com.android.systemui.statusbar.phone.PhoneStatusBar; 56 57import java.util.ArrayList; 58 59/** 60 * An implementation of the Recents component for the current user. For secondary users, this can 61 * be called remotely from the system user. 62 */ 63public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub 64 implements ActivityOptions.OnAnimationStartedListener { 65 66 private final static String TAG = "RecentsImpl"; 67 68 final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab"; 69 final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey"; 70 71 final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; 72 final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; 73 final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity"; 74 75 final static int sMinToggleDelay = 350; 76 77 public final static String RECENTS_PACKAGE = "com.android.systemui"; 78 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 79 80 81 /** 82 * An implementation of ITaskStackListener, that allows us to listen for changes to the system 83 * task stacks and update recents accordingly. 84 */ 85 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable { 86 Handler mHandler; 87 88 public TaskStackListenerImpl(Handler handler) { 89 mHandler = handler; 90 } 91 92 @Override 93 public void onTaskStackChanged() { 94 // Debounce any task stack changes 95 mHandler.removeCallbacks(this); 96 mHandler.post(this); 97 } 98 99 /** Preloads the next task */ 100 public void run() { 101 // TODO: Temporarily skip this if multi stack is enabled 102 /* 103 RecentsConfiguration config = RecentsConfiguration.getInstance(); 104 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 105 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 106 SystemServicesProxy ssp = loader.getSystemServicesProxy(); 107 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask(); 108 109 // Load the next task only if we aren't svelte 110 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 111 loader.preloadTasks(plan, true); 112 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 113 // This callback is made when a new activity is launched and the old one is paused 114 // so ignore the current activity and try and preload the thumbnail for the 115 // previous one. 116 if (runningTaskInfo != null) { 117 launchOpts.runningTaskId = runningTaskInfo.id; 118 } 119 launchOpts.numVisibleTasks = 2; 120 launchOpts.numVisibleTaskThumbnails = 2; 121 launchOpts.onlyLoadForCache = true; 122 launchOpts.onlyLoadPausedActivities = true; 123 loader.loadTasks(mContext, plan, launchOpts); 124 } 125 */ 126 } 127 } 128 129 static RecentsTaskLoadPlan sInstanceLoadPlan; 130 131 Context mContext; 132 SystemServicesProxy mSystemServicesProxy; 133 Handler mHandler; 134 TaskStackListenerImpl mTaskStackListener; 135 RecentsAppWidgetHost mAppWidgetHost; 136 boolean mBootCompleted; 137 boolean mStartAnimationTriggered; 138 boolean mCanReuseTaskStackViews = true; 139 140 // Task launching 141 RecentsConfiguration mConfig; 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 mSystemServicesProxy = new SystemServicesProxy(mContext); 167 mHandler = new Handler(); 168 mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId); 169 Resources res = mContext.getResources(); 170 RecentsTaskLoader.initialize(mContext); 171 LayoutInflater inflater = LayoutInflater.from(mContext); 172 173 // Register the task stack listener 174 mTaskStackListener = new TaskStackListenerImpl(mHandler); 175 mSystemServicesProxy.registerTaskStackListener(mTaskStackListener); 176 177 // Initialize the static configuration resources 178 mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy); 179 mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 180 mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); 181 mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); 182 mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); 183 mDummyStackView = new TaskStackView(mContext, new TaskStack()); 184 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 185 null, false); 186 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */); 187 188 // When we start, preload the data associated with the previous recent tasks. 189 // We can use a new plan since the caches will be the same. 190 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 191 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 192 loader.preloadTasks(plan, true /* isTopTaskHome */); 193 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 194 launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); 195 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 196 launchOpts.onlyLoadForCache = true; 197 loader.loadTasks(mContext, plan, launchOpts); 198 } 199 200 public void onBootCompleted() { 201 mBootCompleted = true; 202 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */); 203 } 204 205 @Override 206 public void onConfigurationChanged() { 207 // Don't reuse task stack views if the configuration changes 208 mCanReuseTaskStackViews = false; 209 mConfig.updateOnConfigurationChange(); 210 } 211 212 /** 213 * This is only called from the system user's Recents. Secondary users will instead proxy their 214 * visibility change events through to the system user via 215 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 216 */ 217 public void onVisibilityChanged(Context context, boolean visible) { 218 SystemUIApplication app = (SystemUIApplication) context; 219 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 220 if (statusBar != null) { 221 statusBar.updateRecentsVisibility(visible); 222 } 223 } 224 225 /** 226 * This is only called from the system user's Recents. Secondary users will instead proxy their 227 * visibility change events through to the system user via 228 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 229 */ 230 public void onStartScreenPinning(Context context) { 231 SystemUIApplication app = (SystemUIApplication) context; 232 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 233 if (statusBar != null) { 234 statusBar.showScreenPinningRequest(false); 235 } 236 } 237 238 @Override 239 public void showRecents(boolean triggeredFromAltTab) { 240 mTriggeredFromAltTab = triggeredFromAltTab; 241 242 try { 243 // Check if the top task is in the home stack, and start the recents activity 244 ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); 245 MutableBoolean isTopTaskHome = new MutableBoolean(true); 246 if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) { 247 startRecentsActivity(topTask, isTopTaskHome.value); 248 } 249 } catch (ActivityNotFoundException e) { 250 Console.logRawError("Failed to launch RecentAppsIntent", e); 251 } 252 } 253 254 @Override 255 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 256 if (mBootCompleted) { 257 // Defer to the activity to handle hiding recents, if it handles it, then it must still 258 // be visible 259 Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY); 260 intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab); 261 intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey); 262 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 263 } 264 } 265 266 @Override 267 public void toggleRecents() { 268 mTriggeredFromAltTab = false; 269 270 try { 271 // If the user has toggled it too quickly, then just eat up the event here (it's better 272 // than showing a janky screenshot). 273 // NOTE: Ideally, the screenshot mechanism would take the window transform into account 274 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { 275 return; 276 } 277 278 // If Recents is the front most activity, then we should just communicate with it 279 // directly to launch the first task or dismiss itself 280 ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); 281 MutableBoolean isTopTaskHome = new MutableBoolean(true); 282 if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) { 283 // Notify recents to toggle itself 284 Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY); 285 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 286 mLastToggleTime = SystemClock.elapsedRealtime(); 287 return; 288 } else { 289 // Otherwise, start the recents activity 290 startRecentsActivity(topTask, isTopTaskHome.value); 291 } 292 } catch (ActivityNotFoundException e) { 293 Console.logRawError("Failed to launch RecentAppsIntent", e); 294 } 295 } 296 297 @Override 298 public void preloadRecents() { 299 // Preload only the raw task list into a new load plan (which will be consumed by the 300 // RecentsActivity) only if there is a task to animate to. 301 ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); 302 MutableBoolean topTaskHome = new MutableBoolean(true); 303 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 304 sInstanceLoadPlan = loader.createLoadPlan(mContext); 305 if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) { 306 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); 307 loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value); 308 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 309 if (stack.getTaskCount() > 0) { 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 int focusedStackId = mSystemServicesProxy.getFocusedStack(); 323 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 324 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 325 loader.preloadTasks(plan, true /* isTopTaskHome */); 326 TaskStack focusedStack = plan.getTaskStack(); 327 328 // Return early if there are no tasks in the focused stack 329 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 330 331 ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask(); 332 // Return early if there is no running task (can't determine affiliated tasks in this case) 333 if (runningTask == null) return; 334 // Return early if the running task is in the home stack (optimization) 335 if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return; 336 337 // Find the task in the recents list 338 ArrayList<Task> tasks = focusedStack.getTasks(); 339 Task toTask = null; 340 ActivityOptions launchOpts = null; 341 int taskCount = tasks.size(); 342 int numAffiliatedTasks = 0; 343 for (int i = 0; i < taskCount; i++) { 344 Task task = tasks.get(i); 345 if (task.key.id == runningTask.id) { 346 TaskGrouping group = task.group; 347 Task.TaskKey toTaskKey; 348 if (showNextTask) { 349 toTaskKey = group.getNextTaskInGroup(task); 350 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 351 R.anim.recents_launch_next_affiliated_task_target, 352 R.anim.recents_launch_next_affiliated_task_source); 353 } else { 354 toTaskKey = group.getPrevTaskInGroup(task); 355 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 356 R.anim.recents_launch_prev_affiliated_task_target, 357 R.anim.recents_launch_prev_affiliated_task_source); 358 } 359 if (toTaskKey != null) { 360 toTask = focusedStack.findTaskWithId(toTaskKey.id); 361 } 362 numAffiliatedTasks = group.getTaskCount(); 363 break; 364 } 365 } 366 367 // Return early if there is no next task 368 if (toTask == null) { 369 if (numAffiliatedTasks > 1) { 370 if (showNextTask) { 371 mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication( 372 ActivityOptions.makeCustomInPlaceAnimation(mContext, 373 R.anim.recents_launch_next_affiliated_task_bounce)); 374 } else { 375 mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication( 376 ActivityOptions.makeCustomInPlaceAnimation(mContext, 377 R.anim.recents_launch_prev_affiliated_task_bounce)); 378 } 379 } 380 return; 381 } 382 383 // Keep track of actually launched affiliated tasks 384 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 385 386 // Launch the task 387 if (toTask.isActive) { 388 // Bring an active task to the foreground 389 mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts); 390 } else { 391 mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id, 392 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 Rect windowRect = mSystemServicesProxy.getWindowRect(); 426 427 // Update the configuration for the current state 428 mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect()); 429 430 if (tryAndBindSearchWidget) { 431 // Try and pre-emptively bind the search widget on startup to ensure that we 432 // have the right thumbnail bounds to animate to. 433 // Note: We have to reload the widget id before we get the task stack bounds below 434 if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 435 mConfig.getSearchBarBounds(windowRect, 436 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 469 // Ensure that we load the running task's icon 470 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 471 launchOpts.runningTaskId = task.id; 472 launchOpts.loadThumbnails = false; 473 launchOpts.onlyLoadForCache = true; 474 RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 475 } 476 477 /** 478 * Caches the header thumbnail used for a window animation asynchronously into 479 * {@link #mThumbnailTransitionBitmapCache}. 480 */ 481 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 482 TaskStack stack, TaskStackView stackView) { 483 preloadIcon(topTask); 484 485 // Update the header bar if necessary 486 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 487 488 // Update the destination rect 489 mDummyStackView.updateMinMaxScrollForStack(stack); 490 final Task toTask = new Task(); 491 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 492 topTask.id, toTask); 493 new AsyncTask<Void, Void, Bitmap>() { 494 @Override 495 protected Bitmap doInBackground(Void... params) { 496 return drawThumbnailTransitionBitmap(toTask, toTransform); 497 } 498 499 @Override 500 protected void onPostExecute(Bitmap bitmap) { 501 mThumbnailTransitionBitmapCache = bitmap; 502 mThumbnailTransitionBitmapCacheKey = toTask; 503 } 504 }.execute(); 505 } 506 507 /** 508 * Creates the activity options for a unknown state->recents transition. 509 */ 510 private ActivityOptions getUnknownTransitionActivityOptions() { 511 mStartAnimationTriggered = false; 512 return ActivityOptions.makeCustomAnimation(mContext, 513 R.anim.recents_from_unknown_enter, 514 R.anim.recents_from_unknown_exit, 515 mHandler, this); 516 } 517 518 /** 519 * Creates the activity options for a home->recents transition. 520 */ 521 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 522 mStartAnimationTriggered = false; 523 if (fromSearchHome) { 524 return ActivityOptions.makeCustomAnimation(mContext, 525 R.anim.recents_from_search_launcher_enter, 526 R.anim.recents_from_search_launcher_exit, 527 mHandler, this); 528 } 529 return ActivityOptions.makeCustomAnimation(mContext, 530 R.anim.recents_from_launcher_enter, 531 R.anim.recents_from_launcher_exit, 532 mHandler, this); 533 } 534 535 /** 536 * Creates the activity options for an app->recents transition. 537 */ 538 private ActivityOptions getThumbnailTransitionActivityOptions( 539 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { 540 541 // Update the destination rect 542 Task toTask = new Task(); 543 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 544 topTask.id, toTask); 545 Rect toTaskRect = toTransform.rect; 546 Bitmap thumbnail; 547 if (mThumbnailTransitionBitmapCacheKey != null 548 && mThumbnailTransitionBitmapCacheKey.key != null 549 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 550 thumbnail = mThumbnailTransitionBitmapCache; 551 mThumbnailTransitionBitmapCacheKey = null; 552 mThumbnailTransitionBitmapCache = null; 553 } else { 554 preloadIcon(topTask); 555 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 556 } 557 if (thumbnail != null) { 558 mStartAnimationTriggered = false; 559 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 560 thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(), 561 toTaskRect.height(), mHandler, this); 562 } 563 564 // If both the screenshot and thumbnail fails, then just fall back to the default transition 565 return getUnknownTransitionActivityOptions(); 566 } 567 568 /** 569 * Returns the transition rect for the given task id. 570 */ 571 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, 572 TaskStackView stackView, int runningTaskId, Task runningTaskOut) { 573 // Find the running task in the TaskStack 574 Task task = null; 575 ArrayList<Task> tasks = stack.getTasks(); 576 if (runningTaskId != -1) { 577 // Otherwise, try and find the task with the 578 int taskCount = tasks.size(); 579 for (int i = taskCount - 1; i >= 0; i--) { 580 Task t = tasks.get(i); 581 if (t.key.id == runningTaskId) { 582 task = t; 583 runningTaskOut.copyFrom(t); 584 break; 585 } 586 } 587 } 588 if (task == null) { 589 // If no task is specified or we can not find the task just use the front most one 590 task = tasks.get(tasks.size() - 1); 591 runningTaskOut.copyFrom(task); 592 } 593 594 // Get the transform for the running task 595 stackView.getScroller().setStackScrollToInitialState(); 596 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 597 stackView.getScroller().getStackScroll(), mTmpTransform, null); 598 return mTmpTransform; 599 } 600 601 /** 602 * Draws the header of a task used for the window animation into a bitmap. 603 */ 604 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 605 if (toTransform != null && toTask.key != null) { 606 Bitmap thumbnail; 607 synchronized (mHeaderBarLock) { 608 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 609 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 610 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 611 Bitmap.Config.ARGB_8888); 612 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 613 thumbnail.eraseColor(0xFFff0000); 614 } else { 615 Canvas c = new Canvas(thumbnail); 616 c.scale(toTransform.scale, toTransform.scale); 617 mHeaderBar.rebindToTask(toTask); 618 mHeaderBar.draw(c); 619 c.setBitmap(null); 620 } 621 } 622 return thumbnail.createAshmemBitmap(); 623 } 624 return null; 625 } 626 627 /** 628 * Shows the recents activity 629 */ 630 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 631 boolean isTopTaskHome) { 632 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 633 634 // Update the header bar if necessary 635 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */); 636 637 if (sInstanceLoadPlan == null) { 638 // Create a new load plan if onPreloadRecents() was never triggered 639 sInstanceLoadPlan = loader.createLoadPlan(mContext); 640 } 641 642 if (!sInstanceLoadPlan.hasTasks()) { 643 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 644 } 645 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 646 647 // Prepare the dummy stack for the transition 648 mDummyStackView.updateMinMaxScrollForStack(stack); 649 TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = 650 mDummyStackView.computeStackVisibilityReport(); 651 boolean hasRecentTasks = stack.getTaskCount() > 0; 652 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 653 654 if (useThumbnailTransition) { 655 // Try starting with a thumbnail transition 656 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 657 mDummyStackView); 658 if (opts != null) { 659 startRecentsActivity(topTask, opts, false /* fromHome */, 660 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 661 } else { 662 // Fall through below to the non-thumbnail transition 663 useThumbnailTransition = false; 664 } 665 } 666 667 if (!useThumbnailTransition) { 668 // If there is no thumbnail transition, but is launching from home into recents, then 669 // use a quick home transition and do the animation from home 670 if (hasRecentTasks) { 671 String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName(); 672 String searchWidgetPackage = 673 Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null); 674 675 // Determine whether we are coming from a search owned home activity 676 boolean fromSearchHome = (homeActivityPackage != null) && 677 homeActivityPackage.equals(searchWidgetPackage); 678 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 679 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 680 false /* fromThumbnail */, stackVr); 681 } else { 682 // Otherwise we do the normal fade from an unknown source 683 ActivityOptions opts = getUnknownTransitionActivityOptions(); 684 startRecentsActivity(topTask, opts, true /* fromHome */, 685 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 686 } 687 } 688 mLastToggleTime = SystemClock.elapsedRealtime(); 689 } 690 691 /** 692 * Starts the recents activity. 693 */ 694 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 695 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, 696 TaskStackViewLayoutAlgorithm.VisibilityReport vr) { 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 /** 723 * Creates a new broadcast intent to send to the Recents activity. 724 * TODO: Use EventBus 725 */ 726 private Intent createLocalBroadcastIntent(Context context, String action) { 727 Intent intent = new Intent(action); 728 intent.setPackage(context.getPackageName()); 729 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | 730 Intent.FLAG_RECEIVER_FOREGROUND); 731 return intent; 732 } 733 734 /**** OnAnimationStartedListener Implementation ****/ 735 736 @Override 737 public void onAnimationStarted() { 738 // Notify recents to start the enter animation 739 // TODO: Use EventBus 740 if (!mStartAnimationTriggered) { 741 // There can be a race condition between the start animation callback and 742 // the start of the new activity (where we register the receiver that listens 743 // to this broadcast, so we add our own receiver and if that gets called, then 744 // we know the activity has not yet started and we can retry sending the broadcast. 745 BroadcastReceiver fallbackReceiver = new BroadcastReceiver() { 746 @Override 747 public void onReceive(Context context, Intent intent) { 748 if (getResultCode() == Activity.RESULT_OK) { 749 mStartAnimationTriggered = true; 750 return; 751 } 752 753 // Schedule for the broadcast to be sent again after some time 754 mHandler.postDelayed(new Runnable() { 755 @Override 756 public void run() { 757 onAnimationStarted(); 758 } 759 }, 25); 760 } 761 }; 762 763 // Send the broadcast to notify Recents that the animation has started 764 Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION); 765 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 766 fallbackReceiver, null, Activity.RESULT_CANCELED, null, null); 767 } 768 } 769} 770