AlternateRecentsComponent.java revision 740c3ac782675d190941b2ab1905e56f246c1b11
1/* 2 * Copyright (C) 2014 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.appwidget.AppWidgetHost; 24import android.appwidget.AppWidgetProviderInfo; 25import android.content.ActivityNotFoundException; 26import android.content.BroadcastReceiver; 27import android.content.ComponentName; 28import android.content.Context; 29import android.content.Intent; 30import android.content.res.Configuration; 31import android.content.res.Resources; 32import android.graphics.Bitmap; 33import android.graphics.Canvas; 34import android.graphics.Rect; 35import android.os.Handler; 36import android.os.SystemClock; 37import android.os.UserHandle; 38import android.util.Pair; 39import android.view.LayoutInflater; 40import android.view.View; 41 42import com.android.systemui.R; 43import com.android.systemui.RecentsComponent; 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; 55 56import java.util.ArrayList; 57import java.util.List; 58import java.util.concurrent.atomic.AtomicBoolean; 59 60 61/** A proxy implementation for the recents component */ 62public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener { 63 64 final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome"; 65 final public static String EXTRA_FROM_SEARCH_HOME = "recents.triggeredOverSearchHome"; 66 final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail"; 67 final public static String EXTRA_FROM_TASK_ID = "recents.activeTaskId"; 68 final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab"; 69 final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey"; 70 final public static String EXTRA_REUSE_TASK_STACK_VIEWS = "recents.reuseTaskStackViews"; 71 final public static String EXTRA_NUM_VISIBLE_TASKS = "recents.numVisibleTasks"; 72 final public static String EXTRA_NUM_VISIBLE_THUMBNAILS = "recents.numVisibleThumbnails"; 73 74 final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; 75 final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; 76 final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity"; 77 78 final static int sMinToggleDelay = 350; 79 80 final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; 81 final static String sRecentsPackage = "com.android.systemui"; 82 final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; 83 84 /** 85 * An implementation of ITaskStackListener, that allows us to listen for changes to the system 86 * task stacks and update recents accordingly. 87 */ 88 class TaskStackListenerImpl extends ITaskStackListener.Stub { 89 @Override 90 public void onTaskStackChanged() { 91 RecentsConfiguration config = RecentsConfiguration.getInstance(); 92 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 93 // Load the next task only if we aren't svelte 94 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 95 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 96 loader.preloadTasks(plan, true /* isTopTaskHome */); 97 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 98 launchOpts.numVisibleTasks = 1; 99 launchOpts.numVisibleTaskThumbnails = 1; 100 launchOpts.onlyLoadForCache = true; 101 loader.loadTasks(mContext, plan, launchOpts); 102 } 103 } 104 } 105 106 static RecentsComponent.Callbacks sRecentsComponentCallbacks; 107 static RecentsTaskLoadPlan sInstanceLoadPlan; 108 109 Context mContext; 110 LayoutInflater mInflater; 111 SystemServicesProxy mSystemServicesProxy; 112 Handler mHandler; 113 TaskStackListenerImpl mTaskStackListener; 114 boolean mBootCompleted; 115 boolean mStartAnimationTriggered; 116 boolean mCanReuseTaskStackViews = true; 117 118 // Task launching 119 RecentsConfiguration mConfig; 120 Rect mWindowRect = new Rect(); 121 Rect mTaskStackBounds = new Rect(); 122 Rect mSystemInsets = new Rect(); 123 TaskViewTransform mTmpTransform = new TaskViewTransform(); 124 int mStatusBarHeight; 125 int mNavBarHeight; 126 int mNavBarWidth; 127 128 // Header (for transition) 129 TaskViewHeader mHeaderBar; 130 TaskStackView mDummyStackView; 131 132 // Variables to keep track of if we need to start recents after binding 133 View mStatusBarView; 134 boolean mTriggeredFromAltTab; 135 long mLastToggleTime; 136 137 public AlternateRecentsComponent(Context context) { 138 RecentsTaskLoader.initialize(context); 139 mInflater = LayoutInflater.from(context); 140 mContext = context; 141 mSystemServicesProxy = new SystemServicesProxy(context); 142 mHandler = new Handler(); 143 mTaskStackBounds = new Rect(); 144 145 // Register the task stack listener 146 mTaskStackListener = new TaskStackListenerImpl(); 147 mSystemServicesProxy.registerTaskStackListener(mTaskStackListener); 148 } 149 150 public void onStart() { 151 // Initialize some static datastructures 152 TaskStackViewLayoutAlgorithm.initializeCurve(); 153 // Load the header bar layout 154 reloadHeaderBarLayout(); 155 // Try and pre-emptively bind the search widget on startup to ensure that we 156 // have the right thumbnail bounds to animate to. 157 if (Constants.DebugFlags.App.EnableSearchLayout) { 158 // If there is no id, then bind a new search app widget 159 if (mConfig.searchBarAppWidgetId < 0) { 160 AppWidgetHost host = new RecentsAppWidgetHost(mContext, 161 Constants.Values.App.AppWidgetHostId); 162 Pair<Integer, AppWidgetProviderInfo> widgetInfo = 163 mSystemServicesProxy.bindSearchAppWidget(host); 164 if (widgetInfo != null) { 165 // Save the app widget id into the settings 166 mConfig.updateSearchBarAppWidgetId(mContext, widgetInfo.first); 167 } 168 } 169 } 170 171 // When we start, preload the data associated with the previous recent tasks. 172 // We can use a new plan since the caches will be the same. 173 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 174 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 175 loader.preloadTasks(plan, true /* isTopTaskHome */); 176 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 177 launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); 178 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 179 launchOpts.onlyLoadForCache = true; 180 loader.loadTasks(mContext, plan, launchOpts); 181 } 182 183 public void onBootCompleted() { 184 mBootCompleted = true; 185 } 186 187 /** Shows the recents */ 188 public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) { 189 mStatusBarView = statusBarView; 190 mTriggeredFromAltTab = triggeredFromAltTab; 191 192 try { 193 startRecentsActivity(); 194 } catch (ActivityNotFoundException e) { 195 Console.logRawError("Failed to launch RecentAppsIntent", e); 196 } 197 } 198 199 /** Hides the recents */ 200 public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 201 if (mBootCompleted) { 202 ActivityManager.RunningTaskInfo topTask = getTopMostTask(); 203 if (topTask != null && isRecentsTopMost(topTask, null)) { 204 // Notify recents to hide itself 205 Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY); 206 intent.setPackage(mContext.getPackageName()); 207 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | 208 Intent.FLAG_RECEIVER_FOREGROUND); 209 intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab); 210 intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey); 211 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 212 } 213 } 214 } 215 216 /** Toggles the alternate recents activity */ 217 public void onToggleRecents(View statusBarView) { 218 mStatusBarView = statusBarView; 219 mTriggeredFromAltTab = false; 220 221 try { 222 toggleRecentsActivity(); 223 } catch (ActivityNotFoundException e) { 224 Console.logRawError("Failed to launch RecentAppsIntent", e); 225 } 226 } 227 228 public void onPreloadRecents() { 229 // Preload only the raw task list into a new load plan (which will be consumed by the 230 // RecentsActivity) 231 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 232 sInstanceLoadPlan = loader.createLoadPlan(mContext); 233 sInstanceLoadPlan.preloadRawTasks(true); 234 } 235 236 public void onCancelPreloadingRecents() { 237 // Do nothing 238 } 239 240 void showRelativeAffiliatedTask(boolean showNextTask) { 241 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 242 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 243 loader.preloadTasks(plan, true /* isTopTaskHome */); 244 TaskStack stack = plan.getTaskStack(); 245 246 // Return early if there are no tasks 247 if (stack.getTaskCount() == 0) return; 248 249 ActivityManager.RunningTaskInfo runningTask = getTopMostTask(); 250 // Return early if there is no running task (can't determine affiliated tasks in this case) 251 if (runningTask == null) return; 252 // Return early if the running task is in the home stack (optimization) 253 if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return; 254 255 // Find the task in the recents list 256 ArrayList<Task> tasks = stack.getTasks(); 257 Task toTask = null; 258 ActivityOptions launchOpts = null; 259 int taskCount = tasks.size(); 260 int numAffiliatedTasks = 0; 261 for (int i = 0; i < taskCount; i++) { 262 Task task = tasks.get(i); 263 if (task.key.id == runningTask.id) { 264 TaskGrouping group = task.group; 265 Task.TaskKey toTaskKey; 266 if (showNextTask) { 267 toTaskKey = group.getNextTaskInGroup(task); 268 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 269 R.anim.recents_launch_next_affiliated_task_target, 270 R.anim.recents_launch_next_affiliated_task_source); 271 } else { 272 toTaskKey = group.getPrevTaskInGroup(task); 273 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 274 R.anim.recents_launch_prev_affiliated_task_target, 275 R.anim.recents_launch_prev_affiliated_task_source); 276 } 277 if (toTaskKey != null) { 278 toTask = stack.findTaskWithId(toTaskKey.id); 279 } 280 numAffiliatedTasks = group.getTaskCount(); 281 break; 282 } 283 } 284 285 // Return early if there is no next task 286 if (toTask == null) { 287 if (numAffiliatedTasks > 1) { 288 if (showNextTask) { 289 mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication( 290 ActivityOptions.makeCustomInPlaceAnimation(mContext, 291 R.anim.recents_launch_next_affiliated_task_bounce)); 292 } else { 293 mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication( 294 ActivityOptions.makeCustomInPlaceAnimation(mContext, 295 R.anim.recents_launch_prev_affiliated_task_bounce)); 296 } 297 } 298 return; 299 } 300 301 // Launch the task 302 if (toTask.isActive) { 303 // Bring an active task to the foreground 304 mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts); 305 } else { 306 mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id, 307 toTask.activityLabel, launchOpts); 308 } 309 } 310 311 public void onShowNextAffiliatedTask() { 312 showRelativeAffiliatedTask(true); 313 } 314 315 public void onShowPrevAffiliatedTask() { 316 showRelativeAffiliatedTask(false); 317 } 318 319 public void onConfigurationChanged(Configuration newConfig) { 320 // Don't reuse task stack views if the configuration changes 321 mCanReuseTaskStackViews = false; 322 // Reload the header bar layout 323 reloadHeaderBarLayout(); 324 } 325 326 /** Prepares the header bar layout. */ 327 void reloadHeaderBarLayout() { 328 Resources res = mContext.getResources(); 329 mWindowRect = mSystemServicesProxy.getWindowRect(); 330 mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 331 mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); 332 mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); 333 mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy); 334 mConfig.updateOnConfigurationChange(); 335 mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, 336 (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds); 337 if (mConfig.isLandscape && mConfig.hasTransposedNavBar) { 338 mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0); 339 } else { 340 mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight); 341 } 342 343 // Inflate the header bar layout so that we can rebind and draw it for the transition 344 TaskStack stack = new TaskStack(); 345 mDummyStackView = new TaskStackView(mContext, stack); 346 TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); 347 Rect taskStackBounds = new Rect(mTaskStackBounds); 348 taskStackBounds.bottom -= mSystemInsets.bottom; 349 algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds); 350 Rect taskViewSize = algo.getUntransformedTaskViewSize(); 351 int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); 352 mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null, 353 false); 354 mHeaderBar.measure( 355 View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY), 356 View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY)); 357 mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight); 358 } 359 360 /** Gets the top task. */ 361 ActivityManager.RunningTaskInfo getTopMostTask() { 362 SystemServicesProxy ssp = mSystemServicesProxy; 363 List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1); 364 if (!tasks.isEmpty()) { 365 return tasks.get(0); 366 } 367 return null; 368 } 369 370 /** Returns whether the recents is currently running */ 371 boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) { 372 SystemServicesProxy ssp = mSystemServicesProxy; 373 if (topTask != null) { 374 ComponentName topActivity = topTask.topActivity; 375 376 // Check if the front most activity is recents 377 if (topActivity.getPackageName().equals(sRecentsPackage) && 378 topActivity.getClassName().equals(sRecentsActivity)) { 379 if (isHomeTopMost != null) { 380 isHomeTopMost.set(false); 381 } 382 return true; 383 } 384 385 if (isHomeTopMost != null) { 386 isHomeTopMost.set(ssp.isInHomeStack(topTask.id)); 387 } 388 } 389 return false; 390 } 391 392 /** Toggles the recents activity */ 393 void toggleRecentsActivity() { 394 // If the user has toggled it too quickly, then just eat up the event here (it's better than 395 // showing a janky screenshot). 396 // NOTE: Ideally, the screenshot mechanism would take the window transform into account 397 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { 398 return; 399 } 400 401 // If Recents is the front most activity, then we should just communicate with it directly 402 // to launch the first task or dismiss itself 403 ActivityManager.RunningTaskInfo topTask = getTopMostTask(); 404 AtomicBoolean isTopTaskHome = new AtomicBoolean(true); 405 if (topTask != null && isRecentsTopMost(topTask, isTopTaskHome)) { 406 // Notify recents to toggle itself 407 Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY); 408 intent.setPackage(mContext.getPackageName()); 409 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | 410 Intent.FLAG_RECEIVER_FOREGROUND); 411 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 412 mLastToggleTime = SystemClock.elapsedRealtime(); 413 return; 414 } else { 415 // Otherwise, start the recents activity 416 startRecentsActivity(topTask, isTopTaskHome.get()); 417 } 418 } 419 420 /** Starts the recents activity if it is not already running */ 421 void startRecentsActivity() { 422 // Check if the top task is in the home stack, and start the recents activity 423 ActivityManager.RunningTaskInfo topTask = getTopMostTask(); 424 AtomicBoolean isTopTaskHome = new AtomicBoolean(true); 425 if (topTask == null || !isRecentsTopMost(topTask, isTopTaskHome)) { 426 startRecentsActivity(topTask, isTopTaskHome.get()); 427 } 428 } 429 430 /** 431 * Creates the activity options for a unknown state->recents transition. 432 */ 433 ActivityOptions getUnknownTransitionActivityOptions() { 434 mStartAnimationTriggered = false; 435 return ActivityOptions.makeCustomAnimation(mContext, 436 R.anim.recents_from_unknown_enter, 437 R.anim.recents_from_unknown_exit, 438 mHandler, this); 439 } 440 441 /** 442 * Creates the activity options for a home->recents transition. 443 */ 444 ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 445 mStartAnimationTriggered = false; 446 if (fromSearchHome) { 447 return ActivityOptions.makeCustomAnimation(mContext, 448 R.anim.recents_from_search_launcher_enter, 449 R.anim.recents_from_search_launcher_exit, 450 mHandler, this); 451 } 452 return ActivityOptions.makeCustomAnimation(mContext, 453 R.anim.recents_from_launcher_enter, 454 R.anim.recents_from_launcher_exit, 455 mHandler, this); 456 } 457 458 /** 459 * Creates the activity options for an app->recents transition. 460 */ 461 ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, 462 TaskStack stack, TaskStackView stackView) { 463 // Update the destination rect 464 Task toTask = new Task(); 465 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, 466 topTask.id, toTask); 467 if (toTransform != null && toTask.key != null) { 468 Rect toTaskRect = toTransform.rect; 469 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 470 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 471 Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 472 Bitmap.Config.ARGB_8888); 473 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { 474 thumbnail.eraseColor(0xFFff0000); 475 } else { 476 Canvas c = new Canvas(thumbnail); 477 c.scale(toTransform.scale, toTransform.scale); 478 mHeaderBar.rebindToTask(toTask); 479 mHeaderBar.draw(c); 480 c.setBitmap(null); 481 } 482 483 mStartAnimationTriggered = false; 484 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView, 485 thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(), 486 toTaskRect.height(), this); 487 } 488 489 // If both the screenshot and thumbnail fails, then just fall back to the default transition 490 return getUnknownTransitionActivityOptions(); 491 } 492 493 /** Returns the transition rect for the given task id. */ 494 TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, 495 int runningTaskId, Task runningTaskOut) { 496 // Find the running task in the TaskStack 497 Task task = null; 498 ArrayList<Task> tasks = stack.getTasks(); 499 if (runningTaskId != -1) { 500 // Otherwise, try and find the task with the 501 int taskCount = tasks.size(); 502 for (int i = taskCount - 1; i >= 0; i--) { 503 Task t = tasks.get(i); 504 if (t.key.id == runningTaskId) { 505 task = t; 506 runningTaskOut.copyFrom(t); 507 break; 508 } 509 } 510 } 511 if (task == null) { 512 // If no task is specified or we can not find the task just use the front most one 513 task = tasks.get(tasks.size() - 1); 514 } 515 516 // Get the transform for the running task 517 stackView.getScroller().setStackScrollToInitialState(); 518 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, 519 stackView.getScroller().getStackScroll(), mTmpTransform, null); 520 return mTmpTransform; 521 } 522 523 /** Starts the recents activity */ 524 void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) { 525 if (sInstanceLoadPlan == null) { 526 // Create a new load plan if onPreloadRecents() was never triggered 527 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 528 sInstanceLoadPlan = loader.createLoadPlan(mContext); 529 } 530 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 531 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); 532 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 533 534 // Prepare the dummy stack for the transition 535 mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); 536 TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = 537 mDummyStackView.computeStackVisibilityReport(); 538 boolean hasRecentTasks = stack.getTaskCount() > 0; 539 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 540 541 if (useThumbnailTransition) { 542 // Ensure that we load the running task's icon 543 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 544 launchOpts.runningTaskId = topTask.id; 545 launchOpts.loadThumbnails = false; 546 launchOpts.onlyLoadForCache = true; 547 loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts); 548 549 // Try starting with a thumbnail transition 550 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, 551 mDummyStackView); 552 if (opts != null) { 553 startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr); 554 } else { 555 // Fall through below to the non-thumbnail transition 556 useThumbnailTransition = false; 557 } 558 } 559 560 if (!useThumbnailTransition) { 561 // If there is no thumbnail transition, but is launching from home into recents, then 562 // use a quick home transition and do the animation from home 563 if (hasRecentTasks) { 564 // Get the home activity info 565 String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName(); 566 // Get the search widget info 567 AppWidgetProviderInfo searchWidget = null; 568 String searchWidgetPackage = null; 569 if (mConfig.hasSearchBarAppWidget()) { 570 searchWidget = mSystemServicesProxy.getAppWidgetInfo( 571 mConfig.searchBarAppWidgetId); 572 } else { 573 searchWidget = mSystemServicesProxy.resolveSearchAppWidget(); 574 } 575 if (searchWidget != null && searchWidget.provider != null) { 576 searchWidgetPackage = searchWidget.provider.getPackageName(); 577 } 578 // Determine whether we are coming from a search owned home activity 579 boolean fromSearchHome = false; 580 if (homeActivityPackage != null && searchWidgetPackage != null && 581 homeActivityPackage.equals(searchWidgetPackage)) { 582 fromSearchHome = true; 583 } 584 585 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 586 startAlternateRecentsActivity(topTask, opts, 587 fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr); 588 } else { 589 // Otherwise we do the normal fade from an unknown source 590 ActivityOptions opts = getUnknownTransitionActivityOptions(); 591 startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr); 592 } 593 } 594 mLastToggleTime = SystemClock.elapsedRealtime(); 595 } 596 597 /** Starts the recents activity */ 598 void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, 599 ActivityOptions opts, String extraFlag, 600 TaskStackViewLayoutAlgorithm.VisibilityReport vr) { 601 Intent intent = new Intent(sToggleRecentsAction); 602 intent.setClassName(sRecentsPackage, sRecentsActivity); 603 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 604 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 605 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 606 if (extraFlag != null) { 607 intent.putExtra(extraFlag, true); 608 } 609 intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab); 610 intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1); 611 intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews); 612 intent.putExtra(EXTRA_NUM_VISIBLE_TASKS, vr.numVisibleTasks); 613 intent.putExtra(EXTRA_NUM_VISIBLE_THUMBNAILS, vr.numVisibleThumbnails); 614 if (opts != null) { 615 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 616 } else { 617 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 618 } 619 mCanReuseTaskStackViews = true; 620 } 621 622 /** Sets the RecentsComponent callbacks. */ 623 public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) { 624 sRecentsComponentCallbacks = cb; 625 } 626 627 /** Notifies the callbacks that the visibility of Recents has changed. */ 628 public static void notifyVisibilityChanged(boolean visible) { 629 if (sRecentsComponentCallbacks != null) { 630 sRecentsComponentCallbacks.onVisibilityChanged(visible); 631 } 632 } 633 634 /** 635 * Returns the preloaded load plan and invalidates it. 636 */ 637 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 638 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 639 sInstanceLoadPlan = null; 640 return plan; 641 } 642 643 /**** OnAnimationStartedListener Implementation ****/ 644 645 @Override 646 public void onAnimationStarted() { 647 // Notify recents to start the enter animation 648 if (!mStartAnimationTriggered) { 649 // There can be a race condition between the start animation callback and 650 // the start of the new activity (where we register the receiver that listens 651 // to this broadcast, so we add our own receiver and if that gets called, then 652 // we know the activity has not yet started and we can retry sending the broadcast. 653 BroadcastReceiver fallbackReceiver = new BroadcastReceiver() { 654 @Override 655 public void onReceive(Context context, Intent intent) { 656 if (getResultCode() == Activity.RESULT_OK) { 657 mStartAnimationTriggered = true; 658 return; 659 } 660 661 // Schedule for the broadcast to be sent again after some time 662 mHandler.postDelayed(new Runnable() { 663 @Override 664 public void run() { 665 onAnimationStarted(); 666 } 667 }, 25); 668 } 669 }; 670 671 // Send the broadcast to notify Recents that the animation has started 672 Intent intent = new Intent(ACTION_START_ENTER_ANIMATION); 673 intent.setPackage(mContext.getPackageName()); 674 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | 675 Intent.FLAG_RECEIVER_FOREGROUND); 676 mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 677 fallbackReceiver, null, Activity.RESULT_CANCELED, null, null); 678 } 679 } 680} 681