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