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