RecentsImpl.java revision 899327f5cbbfb0eae5562b262ccea860c98f6bc4
1/* 2 * Copyright (C) 2015 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 static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 20 21import android.app.ActivityManager; 22import android.app.ActivityOptions; 23import android.app.ITaskStackListener; 24import android.app.UiModeManager; 25import android.appwidget.AppWidgetProviderInfo; 26import android.content.ActivityNotFoundException; 27import android.content.Context; 28import android.content.Intent; 29import android.content.res.Configuration; 30import android.content.res.Resources; 31import android.graphics.Bitmap; 32import android.graphics.Canvas; 33import android.graphics.Rect; 34import android.graphics.RectF; 35import android.os.Handler; 36import android.os.SystemClock; 37import android.os.UserHandle; 38import android.util.Log; 39import android.util.MutableBoolean; 40import android.view.AppTransitionAnimationSpec; 41import android.view.LayoutInflater; 42import android.view.View; 43import android.view.ViewConfiguration; 44 45import com.android.internal.logging.MetricsLogger; 46import com.android.systemui.Prefs; 47import com.android.systemui.R; 48import com.android.systemui.SystemUIApplication; 49import com.android.systemui.recents.events.EventBus; 50import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 51import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; 52import com.android.systemui.recents.events.activity.HideRecentsEvent; 53import com.android.systemui.recents.events.activity.IterateRecentsEvent; 54import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 55import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 56import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 57import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 58import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 59import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 60import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 61import com.android.systemui.recents.misc.DozeTrigger; 62import com.android.systemui.recents.misc.ForegroundThread; 63import com.android.systemui.recents.misc.SystemServicesProxy; 64import com.android.systemui.recents.model.RecentsTaskLoadPlan; 65import com.android.systemui.recents.model.RecentsTaskLoader; 66import com.android.systemui.recents.model.Task; 67import com.android.systemui.recents.model.TaskGrouping; 68import com.android.systemui.recents.model.TaskStack; 69import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; 70import com.android.systemui.recents.views.TaskStackView; 71import com.android.systemui.recents.views.TaskStackViewScroller; 72import com.android.systemui.recents.views.TaskViewHeader; 73import com.android.systemui.recents.views.TaskViewTransform; 74import com.android.systemui.statusbar.BaseStatusBar; 75import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; 76import com.android.systemui.statusbar.phone.PhoneStatusBar; 77 78import java.util.ArrayList; 79 80/** 81 * An implementation of the Recents component for the current user. For secondary users, this can 82 * be called remotely from the system user. 83 */ 84public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener { 85 86 private final static String TAG = "RecentsImpl"; 87 88 // The minimum amount of time between each recents button press that we will handle 89 private final static int MIN_TOGGLE_DELAY_MS = 350; 90 91 // The duration within which the user releasing the alt tab (from when they pressed alt tab) 92 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this 93 // duration, then we will toggle recents after this duration. 94 private final static int FAST_ALT_TAB_DELAY_MS = 225; 95 96 public final static String RECENTS_PACKAGE = "com.android.systemui"; 97 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 98 public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity"; 99 100 //Used to store tv or non-tv activty for use in creating intents. 101 private final String mRecentsIntentActivityName; 102 103 /** 104 * An implementation of ITaskStackListener, that allows us to listen for changes to the system 105 * task stacks and update recents accordingly. 106 */ 107 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable { 108 Handler mHandler; 109 110 public TaskStackListenerImpl(Handler handler) { 111 mHandler = handler; 112 } 113 114 @Override 115 public void onTaskStackChanged() { 116 // Debounce any task stack changes 117 mHandler.removeCallbacks(this); 118 mHandler.post(this); 119 } 120 121 @Override 122 public void onActivityPinned() { 123 } 124 125 @Override 126 public void onPinnedActivityRestartAttempt() { 127 } 128 129 @Override 130 public void onPinnedStackAnimationEnded() { 131 } 132 133 /** Preloads the next task */ 134 public void run() { 135 RecentsConfiguration config = Recents.getConfiguration(); 136 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { 137 RecentsTaskLoader loader = Recents.getTaskLoader(); 138 SystemServicesProxy ssp = Recents.getSystemServices(); 139 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask(); 140 141 // Load the next task only if we aren't svelte 142 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 143 loader.preloadTasks(plan, -1, true /* isTopTaskHome */); 144 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 145 // This callback is made when a new activity is launched and the old one is paused 146 // so ignore the current activity and try and preload the thumbnail for the 147 // previous one. 148 if (runningTaskInfo != null) { 149 launchOpts.runningTaskId = runningTaskInfo.id; 150 } 151 launchOpts.numVisibleTasks = 2; 152 launchOpts.numVisibleTaskThumbnails = 2; 153 launchOpts.onlyLoadForCache = true; 154 launchOpts.onlyLoadPausedActivities = true; 155 loader.loadTasks(mContext, plan, launchOpts); 156 } 157 } 158 } 159 160 private static RecentsTaskLoadPlan sInstanceLoadPlan; 161 162 Context mContext; 163 Handler mHandler; 164 TaskStackListenerImpl mTaskStackListener; 165 RecentsAppWidgetHost mAppWidgetHost; 166 boolean mCanReuseTaskStackViews = true; 167 boolean mDraggingInRecents; 168 boolean mLaunchedWhileDocking; 169 170 // Task launching 171 Rect mSearchBarBounds = new Rect(); 172 Rect mTaskStackBounds = new Rect(); 173 Rect mLastTaskViewBounds = new Rect(); 174 TaskViewTransform mTmpTransform = new TaskViewTransform(); 175 int mStatusBarHeight; 176 int mNavBarHeight; 177 int mNavBarWidth; 178 int mTaskBarHeight; 179 180 // Header (for transition) 181 TaskViewHeader mHeaderBar; 182 final Object mHeaderBarLock = new Object(); 183 TaskStackView mDummyStackView; 184 185 // Variables to keep track of if we need to start recents after binding 186 boolean mTriggeredFromAltTab; 187 long mLastToggleTime; 188 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() { 189 @Override 190 public void run() { 191 // When this fires, then the user has not released alt-tab for at least 192 // FAST_ALT_TAB_DELAY_MS milliseconds 193 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */, 194 false /* reloadTasks */); 195 } 196 }); 197 198 Bitmap mThumbnailTransitionBitmapCache; 199 Task mThumbnailTransitionBitmapCacheKey; 200 201 public RecentsImpl(Context context) { 202 mContext = context; 203 mHandler = new Handler(); 204 mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID); 205 206 // Initialize the static foreground thread 207 ForegroundThread.get(); 208 209 // Register the task stack listener 210 mTaskStackListener = new TaskStackListenerImpl(mHandler); 211 SystemServicesProxy ssp = Recents.getSystemServices(); 212 ssp.registerTaskStackListener(mTaskStackListener); 213 214 // Initialize the static configuration resources 215 reloadHeaderBarLayout(); 216 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */); 217 218 // When we start, preload the data associated with the previous recent tasks. 219 // We can use a new plan since the caches will be the same. 220 RecentsTaskLoader loader = Recents.getTaskLoader(); 221 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 222 loader.preloadTasks(plan, -1, true /* isTopTaskHome */); 223 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 224 launchOpts.numVisibleTasks = loader.getIconCacheSize(); 225 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 226 launchOpts.onlyLoadForCache = true; 227 loader.loadTasks(mContext, plan, launchOpts); 228 229 //Manager used to determine if we are running on tv or not 230 UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); 231 if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 232 mRecentsIntentActivityName = RECENTS_TV_ACTIVITY; 233 } else { 234 mRecentsIntentActivityName = RECENTS_ACTIVITY; 235 } 236 } 237 238 public void onBootCompleted() { 239 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */); 240 } 241 242 public void onConfigurationChanged() { 243 reloadHeaderBarLayout(); 244 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */); 245 // Don't reuse task stack views if the configuration changes 246 mCanReuseTaskStackViews = false; 247 Recents.getConfiguration().updateOnConfigurationChange(); 248 } 249 250 /** 251 * This is only called from the system user's Recents. Secondary users will instead proxy their 252 * visibility change events through to the system user via 253 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 254 */ 255 public void onVisibilityChanged(Context context, boolean visible) { 256 SystemUIApplication app = (SystemUIApplication) context; 257 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 258 if (statusBar != null) { 259 statusBar.updateRecentsVisibility(visible); 260 } 261 } 262 263 /** 264 * This is only called from the system user's Recents. Secondary users will instead proxy their 265 * visibility change events through to the system user via 266 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 267 */ 268 public void onStartScreenPinning(Context context) { 269 SystemUIApplication app = (SystemUIApplication) context; 270 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class); 271 if (statusBar != null) { 272 statusBar.showScreenPinningRequest(false); 273 } 274 } 275 276 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, 277 boolean animate, boolean launchedWhileDockingTask) { 278 mTriggeredFromAltTab = triggeredFromAltTab; 279 mDraggingInRecents = draggingInRecents; 280 mLaunchedWhileDocking = launchedWhileDockingTask; 281 if (mFastAltTabTrigger.isAsleep()) { 282 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset 283 mFastAltTabTrigger.stopDozing(); 284 } else if (mFastAltTabTrigger.isDozing()) { 285 // Fast alt-tab duration has not elapsed. If this is triggered by a different 286 // showRecents() call, then ignore that call for now. 287 // TODO: We can not handle quick tabs that happen between the initial showRecents() call 288 // that started the activity and the activity starting up. The severity of this 289 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though. 290 if (!triggeredFromAltTab) { 291 return; 292 } 293 mFastAltTabTrigger.stopDozing(); 294 } else if (triggeredFromAltTab) { 295 // The fast alt-tab detector is not yet running, so start the trigger and wait for the 296 // hideRecents() call, or for the fast alt-tab duration to elapse 297 mFastAltTabTrigger.startDozing(); 298 return; 299 } 300 301 try { 302 // Check if the top task is in the home stack, and start the recents activity 303 SystemServicesProxy ssp = Recents.getSystemServices(); 304 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 305 MutableBoolean isTopTaskHome = new MutableBoolean(true); 306 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 307 startRecentsActivity(topTask, isTopTaskHome.value, animate); 308 } 309 } catch (ActivityNotFoundException e) { 310 Log.e(TAG, "Failed to launch RecentsActivity", e); 311 } 312 } 313 314 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 315 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) { 316 // The user has released alt-tab before the trigger has run, so just show the next 317 // task immediately 318 showNextTask(); 319 320 // Cancel the fast alt-tab trigger 321 mFastAltTabTrigger.stopDozing(); 322 return; 323 } 324 325 // Defer to the activity to handle hiding recents, if it handles it, then it must still 326 // be visible 327 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab, 328 triggeredFromHomeKey)); 329 } 330 331 public void toggleRecents() { 332 // Skip this toggle if we are already waiting to trigger recents via alt-tab 333 if (mFastAltTabTrigger.isDozing()) { 334 return; 335 } 336 337 mDraggingInRecents = false; 338 mLaunchedWhileDocking = false; 339 mTriggeredFromAltTab = false; 340 341 try { 342 SystemServicesProxy ssp = Recents.getSystemServices(); 343 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 344 MutableBoolean isTopTaskHome = new MutableBoolean(true); 345 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime; 346 347 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) { 348 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 349 RecentsConfiguration config = Recents.getConfiguration(); 350 RecentsActivityLaunchState launchState = config.getLaunchState(); 351 if (!launchState.launchedWithAltTab) { 352 // If the user taps quickly 353 if (!debugFlags.isPagingEnabled() || 354 (ViewConfiguration.getDoubleTapMinTime() < elapsedTime && 355 elapsedTime < ViewConfiguration.getDoubleTapTimeout())) { 356 // Launch the next focused task 357 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 358 } else { 359 // Notify recents to move onto the next task 360 EventBus.getDefault().post(new IterateRecentsEvent()); 361 } 362 } else { 363 // If the user has toggled it too quickly, then just eat up the event here (it's 364 // better than showing a janky screenshot). 365 // NOTE: Ideally, the screenshot mechanism would take the window transform into 366 // account 367 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 368 return; 369 } 370 371 EventBus.getDefault().post(new ToggleRecentsEvent()); 372 mLastToggleTime = SystemClock.elapsedRealtime(); 373 } 374 return; 375 } else { 376 // If the user has toggled it too quickly, then just eat up the event here (it's 377 // better than showing a janky screenshot). 378 // NOTE: Ideally, the screenshot mechanism would take the window transform into 379 // account 380 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 381 return; 382 } 383 384 // Otherwise, start the recents activity 385 startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */); 386 387 // Only close the other system windows if we are actually showing recents 388 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS); 389 mLastToggleTime = SystemClock.elapsedRealtime(); 390 } 391 } catch (ActivityNotFoundException e) { 392 Log.e(TAG, "Failed to launch RecentsActivity", e); 393 } 394 } 395 396 public void preloadRecents() { 397 // Preload only the raw task list into a new load plan (which will be consumed by the 398 // RecentsActivity) only if there is a task to animate to. 399 SystemServicesProxy ssp = Recents.getSystemServices(); 400 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); 401 MutableBoolean topTaskHome = new MutableBoolean(true); 402 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) { 403 RecentsTaskLoader loader = Recents.getTaskLoader(); 404 sInstanceLoadPlan = loader.createLoadPlan(mContext); 405 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); 406 loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value); 407 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 408 if (stack.getTaskCount() > 0) { 409 // We try and draw the thumbnail transition bitmap in parallel before 410 // toggle/show recents is called 411 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView); 412 } 413 } 414 } 415 416 public void cancelPreloadingRecents() { 417 // Do nothing 418 } 419 420 public void onDraggingInRecents(float distanceFromTop) { 421 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop)); 422 } 423 424 public void onDraggingInRecentsEnded(float velocity) { 425 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity)); 426 } 427 428 /** 429 * Transitions to the next recent task in the stack. 430 */ 431 public void showNextTask() { 432 SystemServicesProxy ssp = Recents.getSystemServices(); 433 RecentsTaskLoader loader = Recents.getTaskLoader(); 434 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 435 loader.preloadTasks(plan, -1, true /* isTopTaskHome */); 436 TaskStack focusedStack = plan.getTaskStack(); 437 438 // Return early if there are no tasks in the focused stack 439 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 440 441 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask(); 442 // Return early if there is no running task 443 if (runningTask == null) return; 444 445 // Find the task in the recents list 446 boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId); 447 ArrayList<Task> tasks = focusedStack.getStackTasks(); 448 Task toTask = null; 449 ActivityOptions launchOpts = null; 450 int taskCount = tasks.size(); 451 for (int i = taskCount - 1; i >= 1; i--) { 452 Task task = tasks.get(i); 453 if (isTopTaskHome) { 454 toTask = tasks.get(i - 1); 455 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 456 R.anim.recents_launch_next_affiliated_task_target, 457 R.anim.recents_fast_toggle_app_home_exit); 458 break; 459 } else if (task.key.id == runningTask.id) { 460 toTask = tasks.get(i - 1); 461 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 462 R.anim.recents_launch_prev_affiliated_task_target, 463 R.anim.recents_launch_prev_affiliated_task_source); 464 break; 465 } 466 } 467 468 // Return early if there is no next task 469 if (toTask == null) { 470 ssp.startInPlaceAnimationOnFrontMostApplication( 471 ActivityOptions.makeCustomInPlaceAnimation(mContext, 472 R.anim.recents_launch_prev_affiliated_task_bounce)); 473 return; 474 } 475 476 // Launch the task 477 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts); 478 } 479 480 /** 481 * Transitions to the next affiliated task. 482 */ 483 public void showRelativeAffiliatedTask(boolean showNextTask) { 484 SystemServicesProxy ssp = Recents.getSystemServices(); 485 RecentsTaskLoader loader = Recents.getTaskLoader(); 486 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); 487 loader.preloadTasks(plan, -1, true /* isTopTaskHome */); 488 TaskStack focusedStack = plan.getTaskStack(); 489 490 // Return early if there are no tasks in the focused stack 491 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 492 493 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask(); 494 // Return early if there is no running task (can't determine affiliated tasks in this case) 495 if (runningTask == null) return; 496 // Return early if the running task is in the home stack (optimization) 497 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return; 498 499 // Find the task in the recents list 500 ArrayList<Task> tasks = focusedStack.getStackTasks(); 501 Task toTask = null; 502 ActivityOptions launchOpts = null; 503 int taskCount = tasks.size(); 504 int numAffiliatedTasks = 0; 505 for (int i = 0; i < taskCount; i++) { 506 Task task = tasks.get(i); 507 if (task.key.id == runningTask.id) { 508 TaskGrouping group = task.group; 509 Task.TaskKey toTaskKey; 510 if (showNextTask) { 511 toTaskKey = group.getNextTaskInGroup(task); 512 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 513 R.anim.recents_launch_next_affiliated_task_target, 514 R.anim.recents_launch_next_affiliated_task_source); 515 } else { 516 toTaskKey = group.getPrevTaskInGroup(task); 517 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 518 R.anim.recents_launch_prev_affiliated_task_target, 519 R.anim.recents_launch_prev_affiliated_task_source); 520 } 521 if (toTaskKey != null) { 522 toTask = focusedStack.findTaskWithId(toTaskKey.id); 523 } 524 numAffiliatedTasks = group.getTaskCount(); 525 break; 526 } 527 } 528 529 // Return early if there is no next task 530 if (toTask == null) { 531 if (numAffiliatedTasks > 1) { 532 if (showNextTask) { 533 ssp.startInPlaceAnimationOnFrontMostApplication( 534 ActivityOptions.makeCustomInPlaceAnimation(mContext, 535 R.anim.recents_launch_next_affiliated_task_bounce)); 536 } else { 537 ssp.startInPlaceAnimationOnFrontMostApplication( 538 ActivityOptions.makeCustomInPlaceAnimation(mContext, 539 R.anim.recents_launch_prev_affiliated_task_bounce)); 540 } 541 } 542 return; 543 } 544 545 // Keep track of actually launched affiliated tasks 546 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 547 548 // Launch the task 549 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts); 550 } 551 552 public void showNextAffiliatedTask() { 553 // Keep track of when the affiliated task is triggered 554 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1); 555 showRelativeAffiliatedTask(true); 556 } 557 558 public void showPrevAffiliatedTask() { 559 // Keep track of when the affiliated task is triggered 560 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1); 561 showRelativeAffiliatedTask(false); 562 } 563 564 public void dockTopTask(int topTaskId, int dragMode, 565 int stackCreateMode, Rect initialBounds) { 566 SystemServicesProxy ssp = Recents.getSystemServices(); 567 568 // Make sure we inform DividerView before we actually start the activity so we can change 569 // the resize mode already. 570 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) { 571 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)); 572 showRecents( 573 false /* triggeredFromAltTab */, 574 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS, 575 false /* animate */, 576 true /* launchedWhileDockingTask*/); 577 } 578 } 579 580 /** 581 * Returns the preloaded load plan and invalidates it. 582 */ 583 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 584 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 585 sInstanceLoadPlan = null; 586 return plan; 587 } 588 589 /** 590 * Reloads all the layouts for the header bar transition. 591 */ 592 private void reloadHeaderBarLayout() { 593 Resources res = mContext.getResources(); 594 LayoutInflater inflater = LayoutInflater.from(mContext); 595 596 mStatusBarHeight = res.getDimensionPixelSize( 597 com.android.internal.R.dimen.status_bar_height); 598 mNavBarHeight = res.getDimensionPixelSize( 599 com.android.internal.R.dimen.navigation_bar_height); 600 mNavBarWidth = res.getDimensionPixelSize( 601 com.android.internal.R.dimen.navigation_bar_width); 602 mTaskBarHeight = res.getDimensionPixelSize( 603 R.dimen.recents_task_bar_height); 604 mDummyStackView = new TaskStackView(mContext); 605 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 606 null, false); 607 } 608 609 /** 610 * Prepares the header bar layout for the next transition, if the task view bounds has changed 611 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 612 * 613 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one 614 * is not already bound (can be expensive) 615 * @param stack the stack to initialize the stack layout with 616 */ 617 private void updateHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) { 618 RecentsConfiguration config = Recents.getConfiguration(); 619 SystemServicesProxy ssp = Recents.getSystemServices(); 620 Rect systemInsets = new Rect(); 621 ssp.getStableInsets(systemInsets); 622 Rect windowRect = ssp.getWindowRect(); 623 calculateWindowStableInsets(systemInsets, windowRect); 624 windowRect.offsetTo(0, 0); 625 626 // Update the configuration for the current state 627 config.update(systemInsets); 628 629 if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) { 630 // Try and pre-emptively bind the search widget on startup to ensure that we 631 // have the right thumbnail bounds to animate to. 632 // Note: We have to reload the widget id before we get the task stack bounds below 633 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { 634 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); 635 } 636 } 637 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, 638 mSearchBarBounds, mTaskStackBounds); 639 640 // Rebind the header bar and draw it for the transition 641 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); 642 Rect taskStackBounds = new Rect(mTaskStackBounds); 643 stackLayout.setSystemInsets(systemInsets); 644 if (stack != null) { 645 stackLayout.initialize(taskStackBounds, 646 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); 647 mDummyStackView.setTasks(stack, false /* notifyStackChanges */); 648 } 649 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds(); 650 if (!taskViewBounds.equals(mLastTaskViewBounds)) { 651 mLastTaskViewBounds.set(taskViewBounds); 652 653 int taskViewWidth = taskViewBounds.width(); 654 synchronized (mHeaderBarLock) { 655 mHeaderBar.measure( 656 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), 657 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); 658 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 659 } 660 } 661 } 662 663 /** 664 * Given the stable insets and the rect for our window, calculates the insets that affect our 665 * window. 666 */ 667 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) { 668 Rect displayRect = Recents.getSystemServices().getDisplayRect(); 669 670 // Display rect without insets - available app space 671 Rect appRect = new Rect(displayRect); 672 appRect.inset(inOutInsets); 673 674 // Our window intersected with available app space 675 Rect windowRectWithInsets = new Rect(windowRect); 676 windowRectWithInsets.intersect(appRect); 677 inOutInsets.left = windowRectWithInsets.left - windowRect.left; 678 inOutInsets.top = windowRectWithInsets.top - windowRect.top; 679 inOutInsets.right = windowRect.right - windowRectWithInsets.right; 680 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom; 681 } 682 683 /** 684 * Preloads the icon of a task. 685 */ 686 private void preloadIcon(ActivityManager.RunningTaskInfo task) { 687 // Ensure that we load the running task's icon 688 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 689 launchOpts.runningTaskId = task.id; 690 launchOpts.loadThumbnails = false; 691 launchOpts.onlyLoadForCache = true; 692 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); 693 } 694 695 /** 696 * Caches the header thumbnail used for a window animation asynchronously into 697 * {@link #mThumbnailTransitionBitmapCache}. 698 */ 699 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, 700 TaskStack stack, TaskStackView stackView) { 701 preloadIcon(topTask); 702 703 // Update the header bar if necessary 704 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack); 705 706 // Update the destination rect 707 final Task toTask = new Task(); 708 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask); 709 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() { 710 @Override 711 public void run() { 712 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); 713 mHandler.post(new Runnable() { 714 @Override 715 public void run() { 716 mThumbnailTransitionBitmapCache = transitionBitmap; 717 mThumbnailTransitionBitmapCacheKey = toTask; 718 } 719 }); 720 } 721 }); 722 } 723 724 /** 725 * Creates the activity options for a unknown state->recents transition. 726 */ 727 private ActivityOptions getUnknownTransitionActivityOptions() { 728 return ActivityOptions.makeCustomAnimation(mContext, 729 R.anim.recents_from_unknown_enter, 730 R.anim.recents_from_unknown_exit, 731 mHandler, null); 732 } 733 734 /** 735 * Creates the activity options for a home->recents transition. 736 */ 737 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) { 738 if (fromSearchHome) { 739 return ActivityOptions.makeCustomAnimation(mContext, 740 R.anim.recents_from_search_launcher_enter, 741 R.anim.recents_from_search_launcher_exit, 742 mHandler, null); 743 } 744 return ActivityOptions.makeCustomAnimation(mContext, 745 R.anim.recents_from_launcher_enter, 746 R.anim.recents_from_launcher_exit, 747 mHandler, null); 748 } 749 750 /** 751 * Creates the activity options for an app->recents transition. 752 */ 753 private ActivityOptions getThumbnailTransitionActivityOptions( 754 ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) { 755 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { 756 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); 757 ArrayList<Task> tasks = stackView.getStack().getStackTasks(); 758 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm(); 759 TaskStackViewScroller stackScroller = stackView.getScroller(); 760 761 stackView.updateToInitialState(); 762 763 for (int i = tasks.size() - 1; i >= 0; i--) { 764 Task task = tasks.get(i); 765 if (task.isFreeformTask()) { 766 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, 767 stackScroller.getStackScroll(), mTmpTransform, null); 768 Rect toTaskRect = new Rect(); 769 mTmpTransform.rect.round(toTaskRect); 770 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); 771 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); 772 } 773 } 774 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; 775 specs.toArray(specsArray); 776 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 777 specsArray, mHandler, null, this); 778 } else { 779 // Update the destination rect 780 Task toTask = new Task(); 781 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask); 782 RectF toTaskRect = toTransform.rect; 783 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); 784 if (thumbnail != null) { 785 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, 786 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, 787 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null); 788 } 789 // If both the screenshot and thumbnail fails, then just fall back to the default transition 790 return getUnknownTransitionActivityOptions(); 791 } 792 } 793 794 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, 795 TaskViewTransform toTransform) { 796 Bitmap thumbnail; 797 if (mThumbnailTransitionBitmapCacheKey != null 798 && mThumbnailTransitionBitmapCacheKey.key != null 799 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { 800 thumbnail = mThumbnailTransitionBitmapCache; 801 mThumbnailTransitionBitmapCacheKey = null; 802 mThumbnailTransitionBitmapCache = null; 803 } else { 804 preloadIcon(topTask); 805 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 806 } 807 return thumbnail; 808 } 809 810 /** 811 * Returns the transition rect for the given task id. 812 */ 813 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView, 814 Task runningTaskOut) { 815 // Find the running task in the TaskStack 816 TaskStack stack = stackView.getStack(); 817 Task launchTask = stack.getLaunchTarget(); 818 if (launchTask != null) { 819 runningTaskOut.copyFrom(launchTask); 820 } else { 821 // If no task is specified or we can not find the task just use the front most one 822 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */); 823 runningTaskOut.copyFrom(launchTask); 824 } 825 826 // Get the transform for the running task 827 stackView.updateToInitialState(); 828 mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, 829 stackView.getScroller().getStackScroll(), mTmpTransform, null); 830 return mTmpTransform; 831 } 832 833 /** 834 * Draws the header of a task used for the window animation into a bitmap. 835 */ 836 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { 837 SystemServicesProxy ssp = Recents.getSystemServices(); 838 if (toTransform != null && toTask.key != null) { 839 Bitmap thumbnail; 840 synchronized (mHeaderBarLock) { 841 int toHeaderWidth = (int) toTransform.rect.width(); 842 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); 843 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); 844 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(), 845 (int) toTransform.rect.height()); 846 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, 847 Bitmap.Config.ARGB_8888); 848 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { 849 thumbnail.eraseColor(0xFFff0000); 850 } else { 851 Canvas c = new Canvas(thumbnail); 852 c.scale(toTransform.scale, toTransform.scale); 853 mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */, 854 disabledInSafeMode); 855 mHeaderBar.setDimAlpha(toTransform.dimAlpha); 856 mHeaderBar.draw(c); 857 c.setBitmap(null); 858 } 859 } 860 return thumbnail.createAshmemBitmap(); 861 } 862 return null; 863 } 864 865 /** 866 * Shows the recents activity 867 */ 868 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 869 boolean isTopTaskHome, boolean animate) { 870 RecentsTaskLoader loader = Recents.getTaskLoader(); 871 872 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we 873 // should always preload the tasks now. If we are dragging in recents, reload them as 874 // the stacks might have changed. 875 if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) { 876 // Create a new load plan if preloadRecents() was never triggered 877 sInstanceLoadPlan = loader.createLoadPlan(mContext); 878 } 879 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { 880 loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome); 881 } 882 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 883 884 // Update the header bar if necessary 885 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack); 886 887 // Prepare the dummy stack for the transition 888 TaskStackLayoutAlgorithm.VisibilityReport stackVr = 889 mDummyStackView.computeStackVisibilityReport(); 890 891 if (!animate) { 892 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1); 893 startRecentsActivity(topTask, opts, false /* fromHome */, 894 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr); 895 return; 896 } 897 898 boolean hasRecentTasks = stack.getTaskCount() > 0; 899 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; 900 901 if (useThumbnailTransition) { 902 // Try starting with a thumbnail transition 903 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView); 904 if (opts != null) { 905 startRecentsActivity(topTask, opts, false /* fromHome */, 906 false /* fromSearchHome */, true /* fromThumbnail */, stackVr); 907 } else { 908 // Fall through below to the non-thumbnail transition 909 useThumbnailTransition = false; 910 } 911 } 912 913 if (!useThumbnailTransition) { 914 // If there is no thumbnail transition, but is launching from home into recents, then 915 // use a quick home transition and do the animation from home 916 if (hasRecentTasks) { 917 SystemServicesProxy ssp = Recents.getSystemServices(); 918 String homeActivityPackage = ssp.getHomeActivityPackageName(); 919 String searchWidgetPackage = null; 920 if (RecentsDebugFlags.Static.EnableSearchBar) { 921 searchWidgetPackage = Prefs.getString(mContext, 922 Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null); 923 } else { 924 AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget(); 925 if (searchWidgetInfo != null) { 926 searchWidgetPackage = searchWidgetInfo.provider.getPackageName(); 927 } 928 } 929 930 // Determine whether we are coming from a search owned home activity 931 boolean fromSearchHome = (homeActivityPackage != null) && 932 homeActivityPackage.equals(searchWidgetPackage); 933 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); 934 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome, 935 false /* fromThumbnail */, stackVr); 936 } else { 937 // Otherwise we do the normal fade from an unknown source 938 ActivityOptions opts = getUnknownTransitionActivityOptions(); 939 startRecentsActivity(topTask, opts, true /* fromHome */, 940 false /* fromSearchHome */, false /* fromThumbnail */, stackVr); 941 } 942 } 943 mLastToggleTime = SystemClock.elapsedRealtime(); 944 } 945 946 /** 947 * Starts the recents activity. 948 */ 949 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, 950 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, 951 boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) { 952 // Update the configuration based on the launch options 953 RecentsConfiguration config = Recents.getConfiguration(); 954 RecentsActivityLaunchState launchState = config.getLaunchState(); 955 launchState.launchedFromHome = fromSearchHome || fromHome; 956 launchState.launchedFromSearchHome = fromSearchHome; 957 launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking; 958 launchState.launchedFromAppDocked = mLaunchedWhileDocking; 959 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; 960 launchState.launchedWithAltTab = mTriggeredFromAltTab; 961 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews; 962 launchState.launchedNumVisibleTasks = vr.numVisibleTasks; 963 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails; 964 launchState.launchedHasConfigurationChanged = false; 965 launchState.launchedViaDragGesture = mDraggingInRecents; 966 launchState.launchedWhileDocking = mLaunchedWhileDocking; 967 968 Intent intent = new Intent(); 969 intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName); 970 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 971 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 972 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 973 974 if (opts != null) { 975 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); 976 } else { 977 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 978 } 979 mCanReuseTaskStackViews = true; 980 EventBus.getDefault().send(new RecentsActivityStartingEvent()); 981 } 982 983 /**** OnAnimationFinishedListener Implementation ****/ 984 985 @Override 986 public void onAnimationFinished() { 987 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent()); 988 } 989} 990