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