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