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