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