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 17 package com.android.systemui.recents; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 21 import static android.view.View.MeasureSpec; 22 23 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS; 24 25 import android.app.ActivityManager; 26 import android.app.ActivityOptions; 27 import android.app.trust.TrustManager; 28 import android.content.ActivityNotFoundException; 29 import android.content.ComponentCallbacks2; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.res.Resources; 33 import android.graphics.Bitmap; 34 import android.graphics.Rect; 35 import android.graphics.RectF; 36 import android.graphics.drawable.Drawable; 37 import android.os.Handler; 38 import android.os.SystemClock; 39 import android.util.ArraySet; 40 import android.util.Log; 41 import android.util.MutableBoolean; 42 import android.util.Pair; 43 import android.view.LayoutInflater; 44 import android.view.ViewConfiguration; 45 import android.view.WindowManager; 46 47 import android.widget.Toast; 48 49 import com.android.systemui.Dependency; 50 import com.android.systemui.OverviewProxyService; 51 import com.google.android.collect.Lists; 52 53 import com.android.internal.logging.MetricsLogger; 54 import com.android.internal.policy.DockedDividerUtils; 55 import com.android.systemui.R; 56 import com.android.systemui.SystemUIApplication; 57 import com.android.systemui.recents.events.EventBus; 58 import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 59 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; 60 import com.android.systemui.recents.events.activity.HideRecentsEvent; 61 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; 62 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 63 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 64 import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 65 import com.android.systemui.recents.events.component.ActivityPinnedEvent; 66 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent; 67 import com.android.systemui.recents.events.component.HidePipMenuEvent; 68 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 69 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 70 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 71 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 72 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent; 73 import com.android.systemui.recents.misc.DozeTrigger; 74 import com.android.systemui.recents.misc.ForegroundThread; 75 import com.android.systemui.recents.misc.SystemServicesProxy; 76 import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; 77 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; 78 import com.android.systemui.shared.recents.model.RecentsTaskLoader; 79 import com.android.systemui.shared.recents.model.Task; 80 import com.android.systemui.shared.recents.model.Task.TaskKey; 81 import com.android.systemui.shared.recents.model.TaskStack; 82 import com.android.systemui.shared.recents.model.ThumbnailData; 83 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; 84 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport; 85 import com.android.systemui.recents.views.TaskStackView; 86 import com.android.systemui.recents.views.TaskViewHeader; 87 import com.android.systemui.recents.views.TaskViewTransform; 88 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; 89 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; 90 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; 91 import com.android.systemui.shared.recents.view.RecentsTransition; 92 import com.android.systemui.shared.system.ActivityManagerWrapper; 93 import com.android.systemui.stackdivider.DividerView; 94 import com.android.systemui.statusbar.phone.StatusBar; 95 96 import java.util.ArrayList; 97 import java.util.List; 98 99 /** 100 * An implementation of the Recents component for the current user. For secondary users, this can 101 * be called remotely from the system user. 102 */ 103 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener { 104 105 private final static String TAG = "RecentsImpl"; 106 107 // The minimum amount of time between each recents button press that we will handle 108 private final static int MIN_TOGGLE_DELAY_MS = 350; 109 110 // The duration within which the user releasing the alt tab (from when they pressed alt tab) 111 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this 112 // duration, then we will toggle recents after this duration. 113 private final static int FAST_ALT_TAB_DELAY_MS = 225; 114 115 private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>(); 116 117 public final static String RECENTS_PACKAGE = "com.android.systemui"; 118 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity"; 119 120 /** 121 * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system 122 * task stacks and update recents accordingly. 123 */ 124 class TaskStackListenerImpl extends SysUiTaskStackChangeListener { 125 126 private OverviewProxyService mOverviewProxyService; 127 TaskStackListenerImpl()128 public TaskStackListenerImpl() { 129 mOverviewProxyService = Dependency.get(OverviewProxyService.class); 130 } 131 132 @Override onTaskStackChangedBackground()133 public void onTaskStackChangedBackground() { 134 // Skip background preloading recents in SystemUI if the overview services is bound 135 if (mOverviewProxyService.isEnabled()) { 136 return; 137 } 138 139 // Check this is for the right user 140 if (!checkCurrentUserId(mContext, false /* debug */)) { 141 return; 142 } 143 144 // Preloads the next task 145 RecentsConfiguration config = Recents.getConfiguration(); 146 if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) { 147 Rect windowRect = getWindowRect(null /* windowRectOverride */); 148 if (windowRect.isEmpty()) { 149 return; 150 } 151 152 // Load the next task only if we aren't svelte 153 ActivityManager.RunningTaskInfo runningTaskInfo = 154 ActivityManagerWrapper.getInstance().getRunningTask(); 155 RecentsTaskLoader loader = Recents.getTaskLoader(); 156 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); 157 loader.preloadTasks(plan, -1); 158 TaskStack stack = plan.getTaskStack(); 159 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState(); 160 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 161 162 synchronized (mBackgroundLayoutAlgorithm) { 163 // This callback is made when a new activity is launched and the old one is 164 // paused so ignore the current activity and try and preload the thumbnail for 165 // the previous one. 166 updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect); 167 168 // Launched from app is always the worst case (in terms of how many 169 // thumbnails/tasks visible) 170 launchState.launchedFromApp = true; 171 mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState, 172 -1 /* lastScrollPPresent */); 173 VisibilityReport visibilityReport = 174 mBackgroundLayoutAlgorithm.computeStackVisibilityReport( 175 stack.getTasks()); 176 177 launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1; 178 launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks; 179 launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails; 180 launchOpts.onlyLoadForCache = true; 181 launchOpts.onlyLoadPausedActivities = true; 182 launchOpts.loadThumbnails = true; 183 } 184 loader.loadTasks(plan, launchOpts); 185 } 186 } 187 188 @Override onActivityPinned(String packageName, int userId, int taskId, int stackId)189 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 190 // Check this is for the right user 191 if (!checkCurrentUserId(mContext, false /* debug */)) { 192 return; 193 } 194 195 // This time needs to be fetched the same way the last active time is fetched in 196 // {@link TaskRecord#touchActiveTime} 197 Recents.getConfiguration().getLaunchState().launchedFromPipApp = true; 198 Recents.getConfiguration().getLaunchState().launchedWithNextPipApp = false; 199 EventBus.getDefault().send(new ActivityPinnedEvent(taskId)); 200 consumeInstanceLoadPlan(); 201 sLastPipTime = System.currentTimeMillis(); 202 } 203 204 @Override onActivityUnpinned()205 public void onActivityUnpinned() { 206 // Check this is for the right user 207 if (!checkCurrentUserId(mContext, false /* debug */)) { 208 return; 209 } 210 211 EventBus.getDefault().send(new ActivityUnpinnedEvent()); 212 sLastPipTime = -1; 213 } 214 215 @Override onTaskSnapshotChanged(int taskId, ThumbnailData snapshot)216 public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { 217 // Check this is for the right user 218 if (!checkCurrentUserId(mContext, false /* debug */)) { 219 return; 220 } 221 222 EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot)); 223 } 224 } 225 226 protected static RecentsTaskLoadPlan sInstanceLoadPlan; 227 // Stores the last pinned task time 228 protected static long sLastPipTime = -1; 229 // Stores whether we are waiting for a transition to/from recents to start. During this time, 230 // we disallow the user from manually toggling recents until the transition has started. 231 private static boolean mWaitingForTransitionStart = false; 232 // Stores whether or not the user toggled while we were waiting for a transition to/from 233 // recents. In this case, we defer the toggle state until then and apply it immediately after. 234 private static boolean mToggleFollowingTransitionStart = true; 235 236 private Runnable mResetToggleFlagListener = new Runnable() { 237 @Override 238 public void run() { 239 setWaitingForTransitionStart(false); 240 } 241 }; 242 243 private TrustManager mTrustManager; 244 protected Context mContext; 245 protected Handler mHandler; 246 TaskStackListenerImpl mTaskStackListener; 247 boolean mDraggingInRecents; 248 boolean mLaunchedWhileDocking; 249 250 // Task launching 251 Rect mTmpBounds = new Rect(); 252 TaskViewTransform mTmpTransform = new TaskViewTransform(); 253 int mTaskBarHeight; 254 255 // Header (for transition) 256 TaskViewHeader mHeaderBar; 257 final Object mHeaderBarLock = new Object(); 258 private TaskStackView mDummyStackView; 259 private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm; 260 261 // Variables to keep track of if we need to start recents after binding 262 protected boolean mTriggeredFromAltTab; 263 protected long mLastToggleTime; 264 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() { 265 @Override 266 public void run() { 267 // When this fires, then the user has not released alt-tab for at least 268 // FAST_ALT_TAB_DELAY_MS milliseconds 269 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */, 270 DividerView.INVALID_RECENTS_GROW_TARGET); 271 } 272 }); 273 274 private OverviewProxyService.OverviewProxyListener mOverviewProxyListener = 275 new OverviewProxyService.OverviewProxyListener() { 276 @Override 277 public void onConnectionChanged(boolean isConnected) { 278 if (!isConnected) { 279 // Clear everything when the connection to the overview service 280 Recents.getTaskLoader().onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); 281 } 282 } 283 }; 284 285 // Used to reset the dummy stack view 286 private final TaskStack mEmptyTaskStack = new TaskStack(); 287 RecentsImpl(Context context)288 public RecentsImpl(Context context) { 289 mContext = context; 290 mHandler = new Handler(); 291 mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null); 292 293 // Initialize the static foreground thread 294 ForegroundThread.get(); 295 296 // Register the task stack listener 297 mTaskStackListener = new TaskStackListenerImpl(); 298 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 299 300 // Initialize the static configuration resources 301 mDummyStackView = new TaskStackView(mContext); 302 reloadResources(); 303 304 mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); 305 } 306 onBootCompleted()307 public void onBootCompleted() { 308 // Skip preloading tasks if we are already bound to the service 309 if (Dependency.get(OverviewProxyService.class).isEnabled()) { 310 return; 311 } 312 313 // When we start, preload the data associated with the previous recent tasks. 314 // We can use a new plan since the caches will be the same. 315 RecentsTaskLoader loader = Recents.getTaskLoader(); 316 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); 317 loader.preloadTasks(plan, -1); 318 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 319 launchOpts.numVisibleTasks = loader.getIconCacheSize(); 320 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); 321 launchOpts.onlyLoadForCache = true; 322 loader.loadTasks(plan, launchOpts); 323 } 324 onConfigurationChanged()325 public void onConfigurationChanged() { 326 reloadResources(); 327 mDummyStackView.reloadOnConfigurationChange(); 328 synchronized (mBackgroundLayoutAlgorithm) { 329 mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext); 330 } 331 } 332 333 /** 334 * This is only called from the system user's Recents. Secondary users will instead proxy their 335 * visibility change events through to the system user via 336 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}. 337 */ onVisibilityChanged(Context context, boolean visible)338 public void onVisibilityChanged(Context context, boolean visible) { 339 Recents.getSystemServices().setRecentsVisibility(visible); 340 } 341 342 /** 343 * This is only called from the system user's Recents. Secondary users will instead proxy their 344 * visibility change events through to the system user via 345 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}. 346 */ onStartScreenPinning(Context context, int taskId)347 public void onStartScreenPinning(Context context, int taskId) { 348 final StatusBar statusBar = getStatusBar(); 349 if (statusBar != null) { 350 statusBar.showScreenPinningRequest(taskId, false); 351 } 352 } 353 showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate, int growTarget)354 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, 355 boolean animate, int growTarget) { 356 final SystemServicesProxy ssp = Recents.getSystemServices(); 357 final MutableBoolean isHomeStackVisible = new MutableBoolean(true); 358 final boolean isRecentsVisible = Recents.getSystemServices().isRecentsActivityVisible( 359 isHomeStackVisible); 360 final boolean fromHome = isHomeStackVisible.value; 361 final boolean launchedWhileDockingTask = 362 Recents.getSystemServices().getSplitScreenPrimaryStack() != null; 363 364 mTriggeredFromAltTab = triggeredFromAltTab; 365 mDraggingInRecents = draggingInRecents; 366 mLaunchedWhileDocking = launchedWhileDockingTask; 367 if (mFastAltTabTrigger.isAsleep()) { 368 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset 369 mFastAltTabTrigger.stopDozing(); 370 } else if (mFastAltTabTrigger.isDozing()) { 371 // Fast alt-tab duration has not elapsed. If this is triggered by a different 372 // showRecents() call, then ignore that call for now. 373 // TODO: We can not handle quick tabs that happen between the initial showRecents() call 374 // that started the activity and the activity starting up. The severity of this 375 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though. 376 if (!triggeredFromAltTab) { 377 return; 378 } 379 mFastAltTabTrigger.stopDozing(); 380 } else if (triggeredFromAltTab) { 381 // The fast alt-tab detector is not yet running, so start the trigger and wait for the 382 // hideRecents() call, or for the fast alt-tab duration to elapse 383 mFastAltTabTrigger.startDozing(); 384 return; 385 } 386 387 try { 388 // Check if the top task is in the home stack, and start the recents activity 389 final boolean forceVisible = launchedWhileDockingTask || draggingInRecents; 390 if (forceVisible || !isRecentsVisible) { 391 ActivityManager.RunningTaskInfo runningTask = 392 ActivityManagerWrapper.getInstance().getRunningTask(); 393 startRecentsActivityAndDismissKeyguardIfNeeded(runningTask, 394 isHomeStackVisible.value || fromHome, animate, growTarget); 395 } 396 } catch (ActivityNotFoundException e) { 397 Log.e(TAG, "Failed to launch RecentsActivity", e); 398 } 399 } 400 hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)401 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { 402 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) { 403 // The user has released alt-tab before the trigger has run, so just show the next 404 // task immediately 405 showNextTask(); 406 407 // Cancel the fast alt-tab trigger 408 mFastAltTabTrigger.stopDozing(); 409 return; 410 } 411 412 // Defer to the activity to handle hiding recents, if it handles it, then it must still 413 // be visible 414 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab, 415 triggeredFromHomeKey)); 416 } 417 toggleRecents(int growTarget)418 public void toggleRecents(int growTarget) { 419 if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { 420 return; 421 } 422 423 // Skip this toggle if we are already waiting to trigger recents via alt-tab 424 if (mFastAltTabTrigger.isDozing()) { 425 return; 426 } 427 428 if (mWaitingForTransitionStart) { 429 mToggleFollowingTransitionStart = true; 430 return; 431 } 432 433 mDraggingInRecents = false; 434 mLaunchedWhileDocking = false; 435 mTriggeredFromAltTab = false; 436 437 try { 438 MutableBoolean isHomeStackVisible = new MutableBoolean(true); 439 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime; 440 441 SystemServicesProxy ssp = Recents.getSystemServices(); 442 if (ssp.isRecentsActivityVisible(isHomeStackVisible)) { 443 RecentsConfiguration config = Recents.getConfiguration(); 444 RecentsActivityLaunchState launchState = config.getLaunchState(); 445 if (!launchState.launchedWithAltTab) { 446 if (Recents.getConfiguration().isGridEnabled) { 447 // Has the user tapped quickly? 448 boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout(); 449 if (isQuickTap) { 450 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 451 } else { 452 EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent()); 453 } 454 } else { 455 // Launch the next focused task 456 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 457 } 458 } else { 459 // If the user has toggled it too quickly, then just eat up the event here (it's 460 // better than showing a janky screenshot). 461 // NOTE: Ideally, the screenshot mechanism would take the window transform into 462 // account 463 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 464 return; 465 } 466 467 EventBus.getDefault().post(new ToggleRecentsEvent()); 468 mLastToggleTime = SystemClock.elapsedRealtime(); 469 } 470 return; 471 } else { 472 // If the user has toggled it too quickly, then just eat up the event here (it's 473 // better than showing a janky screenshot). 474 // NOTE: Ideally, the screenshot mechanism would take the window transform into 475 // account 476 if (elapsedTime < MIN_TOGGLE_DELAY_MS) { 477 return; 478 } 479 480 // Otherwise, start the recents activity 481 ActivityManager.RunningTaskInfo runningTask = 482 ActivityManagerWrapper.getInstance().getRunningTask(); 483 startRecentsActivityAndDismissKeyguardIfNeeded(runningTask, 484 isHomeStackVisible.value, true /* animate */, growTarget); 485 486 // Only close the other system windows if we are actually showing recents 487 ActivityManagerWrapper.getInstance().closeSystemWindows( 488 SYSTEM_DIALOG_REASON_RECENT_APPS); 489 mLastToggleTime = SystemClock.elapsedRealtime(); 490 } 491 } catch (ActivityNotFoundException e) { 492 Log.e(TAG, "Failed to launch RecentsActivity", e); 493 } 494 } 495 496 public void preloadRecents() { 497 if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { 498 return; 499 } 500 501 // Skip preloading recents when keyguard is showing 502 final StatusBar statusBar = getStatusBar(); 503 if (statusBar != null && statusBar.isKeyguardShowing()) { 504 return; 505 } 506 507 // Preload only the raw task list into a new load plan (which will be consumed by the 508 // RecentsActivity) only if there is a task to animate to. Post this to ensure that we 509 // don't block the touch feedback on the nav bar button which triggers this. 510 mHandler.post(() -> { 511 SystemServicesProxy ssp = Recents.getSystemServices(); 512 if (!ssp.isRecentsActivityVisible(null)) { 513 ActivityManager.RunningTaskInfo runningTask = 514 ActivityManagerWrapper.getInstance().getRunningTask(); 515 if (runningTask == null) { 516 return; 517 } 518 519 RecentsTaskLoader loader = Recents.getTaskLoader(); 520 sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); 521 loader.preloadTasks(sInstanceLoadPlan, runningTask.id); 522 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 523 if (stack.getTaskCount() > 0) { 524 // Only preload the icon (but not the thumbnail since it may not have been taken 525 // for the pausing activity) 526 preloadIcon(runningTask.id); 527 528 // At this point, we don't know anything about the stack state. So only 529 // calculate the dimensions of the thumbnail that we need for the transition 530 // into Recents, but do not draw it until we construct the activity options when 531 // we start Recents 532 updateHeaderBarLayout(stack, null /* window rect override*/); 533 } 534 } 535 }); 536 } 537 538 public void cancelPreloadingRecents() { 539 // Do nothing 540 } 541 542 public void onDraggingInRecents(float distanceFromTop) { 543 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop)); 544 } 545 546 public void onDraggingInRecentsEnded(float velocity) { 547 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity)); 548 } 549 550 public void onShowCurrentUserToast(int msgResId, int msgLength) { 551 Toast.makeText(mContext, msgResId, msgLength).show(); 552 } 553 554 /** 555 * Transitions to the next recent task in the stack. 556 */ 557 public void showNextTask() { 558 SystemServicesProxy ssp = Recents.getSystemServices(); 559 RecentsTaskLoader loader = Recents.getTaskLoader(); 560 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); 561 loader.preloadTasks(plan, -1); 562 TaskStack focusedStack = plan.getTaskStack(); 563 564 // Return early if there are no tasks in the focused stack 565 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 566 567 // Return early if there is no running task 568 ActivityManager.RunningTaskInfo runningTask = 569 ActivityManagerWrapper.getInstance().getRunningTask(); 570 if (runningTask == null) return; 571 572 // Find the task in the recents list 573 boolean isRunningTaskInHomeStack = 574 runningTask.configuration.windowConfiguration.getActivityType() 575 == ACTIVITY_TYPE_HOME; 576 ArrayList<Task> tasks = focusedStack.getTasks(); 577 Task toTask = null; 578 ActivityOptions launchOpts = null; 579 int taskCount = tasks.size(); 580 for (int i = taskCount - 1; i >= 1; i--) { 581 Task task = tasks.get(i); 582 if (isRunningTaskInHomeStack) { 583 toTask = tasks.get(i - 1); 584 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 585 R.anim.recents_launch_next_affiliated_task_target, 586 R.anim.recents_fast_toggle_app_home_exit); 587 break; 588 } else if (task.key.id == runningTask.id) { 589 toTask = tasks.get(i - 1); 590 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 591 R.anim.recents_launch_prev_affiliated_task_target, 592 R.anim.recents_launch_prev_affiliated_task_source); 593 break; 594 } 595 } 596 597 // Return early if there is no next task 598 if (toTask == null) { 599 ssp.startInPlaceAnimationOnFrontMostApplication( 600 ActivityOptions.makeCustomInPlaceAnimation(mContext, 601 R.anim.recents_launch_prev_affiliated_task_bounce)); 602 return; 603 } 604 605 // Launch the task 606 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts, 607 null /* resultCallback */, null /* resultCallbackHandler */); 608 } 609 610 /** 611 * Transitions to the next affiliated task. 612 */ showRelativeAffiliatedTask(boolean showNextTask)613 public void showRelativeAffiliatedTask(boolean showNextTask) { 614 SystemServicesProxy ssp = Recents.getSystemServices(); 615 RecentsTaskLoader loader = Recents.getTaskLoader(); 616 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext); 617 loader.preloadTasks(plan, -1); 618 TaskStack focusedStack = plan.getTaskStack(); 619 620 // Return early if there are no tasks in the focused stack 621 if (focusedStack == null || focusedStack.getTaskCount() == 0) return; 622 623 // Return early if there is no running task (can't determine affiliated tasks in this case) 624 ActivityManager.RunningTaskInfo runningTask = 625 ActivityManagerWrapper.getInstance().getRunningTask(); 626 final int activityType = runningTask.configuration.windowConfiguration.getActivityType(); 627 if (runningTask == null) return; 628 // Return early if the running task is in the home/recents stack (optimization) 629 if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return; 630 631 // Find the task in the recents list 632 ArrayList<Task> tasks = focusedStack.getTasks(); 633 Task toTask = null; 634 ActivityOptions launchOpts = null; 635 int taskCount = tasks.size(); 636 for (int i = 0; i < taskCount; i++) { 637 Task task = tasks.get(i); 638 if (task.key.id == runningTask.id) { 639 if (showNextTask) { 640 if ((i + 1) < taskCount) { 641 toTask = tasks.get(i + 1); 642 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 643 R.anim.recents_launch_next_affiliated_task_target, 644 R.anim.recents_launch_next_affiliated_task_source); 645 } 646 } else { 647 if ((i - 1) >= 0) { 648 toTask = tasks.get(i - 1); 649 launchOpts = ActivityOptions.makeCustomAnimation(mContext, 650 R.anim.recents_launch_prev_affiliated_task_target, 651 R.anim.recents_launch_prev_affiliated_task_source); 652 } 653 } 654 break; 655 } 656 } 657 658 // Return early if there is no next task 659 if (toTask == null) { 660 if (showNextTask) { 661 ssp.startInPlaceAnimationOnFrontMostApplication( 662 ActivityOptions.makeCustomInPlaceAnimation(mContext, 663 R.anim.recents_launch_next_affiliated_task_bounce)); 664 } else { 665 ssp.startInPlaceAnimationOnFrontMostApplication( 666 ActivityOptions.makeCustomInPlaceAnimation(mContext, 667 R.anim.recents_launch_prev_affiliated_task_bounce)); 668 } 669 return; 670 } 671 672 // Keep track of actually launched affiliated tasks 673 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); 674 675 // Launch the task 676 ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts, 677 null /* resultListener */, null /* resultCallbackHandler */); 678 } 679 showNextAffiliatedTask()680 public void showNextAffiliatedTask() { 681 // Keep track of when the affiliated task is triggered 682 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1); 683 showRelativeAffiliatedTask(true); 684 } 685 showPrevAffiliatedTask()686 public void showPrevAffiliatedTask() { 687 // Keep track of when the affiliated task is triggered 688 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1); 689 showRelativeAffiliatedTask(false); 690 } 691 splitPrimaryTask(int taskId, int dragMode, int stackCreateMode, Rect initialBounds)692 public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode, 693 Rect initialBounds) { 694 SystemServicesProxy ssp = Recents.getSystemServices(); 695 696 // Make sure we inform DividerView before we actually start the activity so we can change 697 // the resize mode already. 698 if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) { 699 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)); 700 } 701 } 702 setWaitingForTransitionStart(boolean waitingForTransitionStart)703 public void setWaitingForTransitionStart(boolean waitingForTransitionStart) { 704 if (mWaitingForTransitionStart == waitingForTransitionStart) { 705 return; 706 } 707 708 mWaitingForTransitionStart = waitingForTransitionStart; 709 if (!waitingForTransitionStart && mToggleFollowingTransitionStart) { 710 mHandler.post(() -> toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET)); 711 } 712 mToggleFollowingTransitionStart = false; 713 } 714 715 /** 716 * Returns the preloaded load plan and invalidates it. 717 */ consumeInstanceLoadPlan()718 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { 719 RecentsTaskLoadPlan plan = sInstanceLoadPlan; 720 sInstanceLoadPlan = null; 721 return plan; 722 } 723 724 /** 725 * @return the time at which a task last entered picture-in-picture. 726 */ getLastPipTime()727 public static long getLastPipTime() { 728 return sLastPipTime; 729 } 730 731 /** 732 * Clears the time at which a task last entered picture-in-picture. 733 */ clearLastPipTime()734 public static void clearLastPipTime() { 735 sLastPipTime = -1; 736 } 737 738 /** 739 * Reloads all the resources for the current configuration. 740 */ reloadResources()741 private void reloadResources() { 742 Resources res = mContext.getResources(); 743 744 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext, 745 R.dimen.recents_task_view_header_height, 746 R.dimen.recents_task_view_header_height, 747 R.dimen.recents_task_view_header_height, 748 R.dimen.recents_task_view_header_height_tablet_land, 749 R.dimen.recents_task_view_header_height, 750 R.dimen.recents_task_view_header_height_tablet_land, 751 R.dimen.recents_grid_task_view_header_height); 752 753 LayoutInflater inflater = LayoutInflater.from(mContext); 754 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, 755 null, false); 756 mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection()); 757 } 758 updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout, TaskStack stack, Rect windowRect)759 private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout, 760 TaskStack stack, Rect windowRect) { 761 SystemServicesProxy ssp = Recents.getSystemServices(); 762 Rect displayRect = ssp.getDisplayRect(); 763 Rect systemInsets = new Rect(); 764 ssp.getStableInsets(systemInsets); 765 766 // When docked, the nav bar insets are consumed and the activity is measured without insets. 767 // However, the window bounds include the insets, so we need to subtract them here to make 768 // them identical. 769 if (ssp.hasDockedTask()) { 770 if (systemInsets.bottom < windowRect.height()) { 771 // Only apply inset if it isn't going to cause the rect height to go negative. 772 windowRect.bottom -= systemInsets.bottom; 773 } 774 systemInsets.bottom = 0; 775 } 776 calculateWindowStableInsets(systemInsets, windowRect, displayRect); 777 windowRect.offsetTo(0, 0); 778 779 // Rebind the header bar and draw it for the transition 780 stackLayout.setSystemInsets(systemInsets); 781 if (stack != null) { 782 stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top, 783 systemInsets.left, systemInsets.right, mTmpBounds); 784 stackLayout.reset(); 785 stackLayout.initialize(displayRect, windowRect, mTmpBounds); 786 } 787 } 788 getWindowRect(Rect windowRectOverride)789 private Rect getWindowRect(Rect windowRectOverride) { 790 return windowRectOverride != null 791 ? new Rect(windowRectOverride) 792 : Recents.getSystemServices().getWindowRect(); 793 } 794 795 /** 796 * Prepares the header bar layout for the next transition, if the task view bounds has changed 797 * since the last call, it will attempt to re-measure and layout the header bar to the new size. 798 * 799 * @param stack the stack to initialize the stack layout with 800 * @param windowRectOverride the rectangle to use when calculating the stack state which can 801 * be different from the current window rect if recents is resizing 802 * while being launched 803 */ updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride)804 private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) { 805 Rect windowRect = getWindowRect(windowRectOverride); 806 int taskViewWidth = 0; 807 boolean useGridLayout = mDummyStackView.useGridLayout(); 808 updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect); 809 if (stack != null) { 810 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); 811 mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */); 812 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */); 813 // Get the width of a task view so that we know how wide to draw the header bar. 814 if (useGridLayout) { 815 TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm(); 816 gridLayout.initialize(windowRect); 817 taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */, 818 stack.getTaskCount(), new TaskViewTransform(), 819 stackLayout).rect.width(); 820 } else { 821 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds(); 822 if (!taskViewBounds.isEmpty()) { 823 taskViewWidth = taskViewBounds.width(); 824 } 825 } 826 } 827 828 if (stack != null && taskViewWidth > 0) { 829 synchronized (mHeaderBarLock) { 830 if (mHeaderBar.getMeasuredWidth() != taskViewWidth || 831 mHeaderBar.getMeasuredHeight() != mTaskBarHeight) { 832 if (useGridLayout) { 833 mHeaderBar.setShouldDarkenBackgroundColor(true); 834 mHeaderBar.setNoUserInteractionState(); 835 } 836 mHeaderBar.forceLayout(); 837 mHeaderBar.measure( 838 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY), 839 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY)); 840 } 841 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); 842 } 843 } 844 } 845 846 /** 847 * Given the stable insets and the rect for our window, calculates the insets that affect our 848 * window. 849 */ calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect)850 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) { 851 852 // Display rect without insets - available app space 853 Rect appRect = new Rect(displayRect); 854 appRect.inset(inOutInsets); 855 856 // Our window intersected with available app space 857 Rect windowRectWithInsets = new Rect(windowRect); 858 windowRectWithInsets.intersect(appRect); 859 inOutInsets.left = windowRectWithInsets.left - windowRect.left; 860 inOutInsets.top = windowRectWithInsets.top - windowRect.top; 861 inOutInsets.right = windowRect.right - windowRectWithInsets.right; 862 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom; 863 } 864 865 /** 866 * Preloads the icon of a task. 867 */ preloadIcon(int runningTaskId)868 private void preloadIcon(int runningTaskId) { 869 // Ensure that we load the running task's icon 870 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); 871 launchOpts.runningTaskId = runningTaskId; 872 launchOpts.loadThumbnails = false; 873 launchOpts.onlyLoadForCache = true; 874 Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts); 875 } 876 877 /** 878 * Creates the activity options for a unknown state->recents transition. 879 */ getUnknownTransitionActivityOptions()880 protected ActivityOptions getUnknownTransitionActivityOptions() { 881 return ActivityOptions.makeCustomAnimation(mContext, 882 R.anim.recents_from_unknown_enter, 883 R.anim.recents_from_unknown_exit, 884 mHandler, null); 885 } 886 887 /** 888 * Creates the activity options for a home->recents transition. 889 */ getHomeTransitionActivityOptions()890 protected ActivityOptions getHomeTransitionActivityOptions() { 891 return ActivityOptions.makeCustomAnimation(mContext, 892 R.anim.recents_from_launcher_enter, 893 R.anim.recents_from_launcher_exit, 894 mHandler, null); 895 } 896 897 /** 898 * Creates the activity options for an app->recents transition. 899 */ 900 private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect)901 getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask, 902 Rect windowOverrideRect) { 903 final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice; 904 905 // Update the destination rect 906 Task toTask = new Task(); 907 TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask, 908 windowOverrideRect); 909 910 RectF toTaskRect = toTransform.rect; 911 AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) { 912 @Override 913 public List<AppTransitionAnimationSpecCompat> composeSpecs() { 914 Rect rect = new Rect(); 915 toTaskRect.round(rect); 916 Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); 917 return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id, 918 thumbnail, rect)); 919 } 920 }; 921 922 // For low end ram devices, wait for transition flag is reset when Recents entrance 923 // animation is complete instead of when the transition animation starts 924 return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler, 925 false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener), 926 future); 927 } 928 929 /** 930 * Returns the transition rect for the given task id. 931 */ getThumbnailTransitionTransform(TaskStackView stackView, Task runningTaskOut, Rect windowOverrideRect)932 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView, 933 Task runningTaskOut, Rect windowOverrideRect) { 934 // Find the running task in the TaskStack 935 TaskStack stack = stackView.getStack(); 936 Task launchTask = stack.getLaunchTarget(); 937 if (launchTask != null) { 938 runningTaskOut.copyFrom(launchTask); 939 } else { 940 // If no task is specified or we can not find the task just use the front most one 941 launchTask = stack.getFrontMostTask(); 942 runningTaskOut.copyFrom(launchTask); 943 } 944 945 // Get the transform for the running task 946 stackView.updateLayoutAlgorithm(true /* boundScroll */); 947 stackView.updateToInitialState(); 948 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, 949 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect); 950 return mTmpTransform; 951 } 952 953 /** 954 * Draws the header of a task used for the window animation into a bitmap. 955 */ drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform)956 private Bitmap drawThumbnailTransitionBitmap(Task toTask, 957 TaskViewTransform toTransform) { 958 SystemServicesProxy ssp = Recents.getSystemServices(); 959 int width = (int) toTransform.rect.width(); 960 int height = (int) toTransform.rect.height(); 961 if (toTransform != null && toTask.key != null && width > 0 && height > 0) { 962 synchronized (mHeaderBarLock) { 963 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); 964 mHeaderBar.onTaskViewSizeChanged(width, height); 965 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { 966 return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight, 967 null, 1f, 0xFFff0000); 968 } else { 969 // Workaround for b/27815919, reset the callback so that we do not trigger an 970 // invalidate on the header bar as a result of updating the icon 971 Drawable icon = mHeaderBar.getIconView().getDrawable(); 972 if (icon != null) { 973 icon.setCallback(null); 974 } 975 mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */, 976 disabledInSafeMode); 977 mHeaderBar.onTaskDataLoaded(); 978 mHeaderBar.setDimAlpha(toTransform.dimAlpha); 979 return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight, 980 mHeaderBar, 1f, 0); 981 } 982 } 983 } 984 return null; 985 } 986 987 /** 988 * Shows the recents activity after dismissing the keyguard if visible 989 */ startRecentsActivityAndDismissKeyguardIfNeeded( final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible, final boolean animate, final int growTarget)990 protected void startRecentsActivityAndDismissKeyguardIfNeeded( 991 final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible, 992 final boolean animate, final int growTarget) { 993 // Preload only if device for current user is unlocked 994 final StatusBar statusBar = getStatusBar(); 995 if (statusBar != null && statusBar.isKeyguardShowing()) { 996 statusBar.executeRunnableDismissingKeyguard(() -> { 997 // Flush trustmanager before checking device locked per user when preloading 998 mTrustManager.reportKeyguardShowingChanged(); 999 mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible, 1000 animate, growTarget)); 1001 }, null, true /* dismissShade */, false /* afterKeyguardGone */, 1002 true /* deferred */); 1003 } else { 1004 startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget); 1005 } 1006 } 1007 startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, boolean isHomeStackVisible, boolean animate, int growTarget)1008 private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, 1009 boolean isHomeStackVisible, boolean animate, int growTarget) { 1010 RecentsTaskLoader loader = Recents.getTaskLoader(); 1011 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 1012 1013 int runningTaskId = !mLaunchedWhileDocking && (runningTask != null) 1014 ? runningTask.id 1015 : -1; 1016 1017 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we 1018 // should always preload the tasks now. If we are dragging in recents, reload them as 1019 // the stacks might have changed. 1020 if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) { 1021 // Create a new load plan if preloadRecents() was never triggered 1022 sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext); 1023 } 1024 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { 1025 loader.preloadTasks(sInstanceLoadPlan, runningTaskId); 1026 } 1027 1028 TaskStack stack = sInstanceLoadPlan.getTaskStack(); 1029 boolean hasRecentTasks = stack.getTaskCount() > 0; 1030 boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible && 1031 hasRecentTasks; 1032 1033 // Update the launch state that we need in updateHeaderBarLayout() 1034 launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking; 1035 launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking; 1036 launchState.launchedFromPipApp = false; 1037 launchState.launchedWithNextPipApp = 1038 stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime()); 1039 launchState.launchedViaDockGesture = mLaunchedWhileDocking; 1040 launchState.launchedViaDragGesture = mDraggingInRecents; 1041 launchState.launchedToTaskId = runningTaskId; 1042 launchState.launchedWithAltTab = mTriggeredFromAltTab; 1043 1044 // Disable toggling of recents between starting the activity and it is visible and the app 1045 // has started its transition into recents. 1046 setWaitingForTransitionStart(useThumbnailTransition); 1047 1048 // Preload the icon (this will be a null-op if we have preloaded the icon already in 1049 // preloadRecents()) 1050 preloadIcon(runningTaskId); 1051 1052 // Update the header bar if necessary 1053 Rect windowOverrideRect = getWindowRectOverride(growTarget); 1054 updateHeaderBarLayout(stack, windowOverrideRect); 1055 1056 // Prepare the dummy stack for the transition 1057 TaskStackLayoutAlgorithm.VisibilityReport stackVr = 1058 mDummyStackView.computeStackVisibilityReport(); 1059 1060 // Update the remaining launch state 1061 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks; 1062 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails; 1063 1064 if (!animate) { 1065 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1), 1066 null /* future */); 1067 return; 1068 } 1069 1070 Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair; 1071 if (useThumbnailTransition) { 1072 // Try starting with a thumbnail transition 1073 pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect); 1074 } else { 1075 // If there is no thumbnail transition, but is launching from home into recents, then 1076 // use a quick home transition 1077 pair = new Pair<>(hasRecentTasks 1078 ? getHomeTransitionActivityOptions() 1079 : getUnknownTransitionActivityOptions(), null); 1080 } 1081 startRecentsActivity(pair.first, pair.second); 1082 mLastToggleTime = SystemClock.elapsedRealtime(); 1083 } 1084 getWindowRectOverride(int growTarget)1085 private Rect getWindowRectOverride(int growTarget) { 1086 if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) { 1087 return SystemServicesProxy.getInstance(mContext).getWindowRect(); 1088 } 1089 Rect result = new Rect(); 1090 Rect displayRect = Recents.getSystemServices().getDisplayRect(); 1091 DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM, 1092 result, displayRect.width(), displayRect.height(), 1093 Recents.getSystemServices().getDockedDividerSize(mContext)); 1094 return result; 1095 } 1096 getStatusBar()1097 private StatusBar getStatusBar() { 1098 return ((SystemUIApplication) mContext).getComponent(StatusBar.class); 1099 } 1100 1101 /** 1102 * Starts the recents activity. 1103 */ startRecentsActivity(ActivityOptions opts, final AppTransitionAnimationSpecsFuture future)1104 private void startRecentsActivity(ActivityOptions opts, 1105 final AppTransitionAnimationSpecsFuture future) { 1106 Intent intent = new Intent(); 1107 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY); 1108 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1109 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 1110 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 1111 HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent(); 1112 hideMenuEvent.addPostAnimationCallback(() -> { 1113 Recents.getSystemServices().startActivityAsUserAsync(intent, opts); 1114 EventBus.getDefault().send(new RecentsActivityStartingEvent()); 1115 if (future != null) { 1116 future.composeSpecsSynchronous(); 1117 } 1118 }); 1119 EventBus.getDefault().send(hideMenuEvent); 1120 1121 // Once we have launched the activity, reset the dummy stack view tasks so we don't hold 1122 // onto references to the same tasks consumed by the activity 1123 mDummyStackView.setTasks(mEmptyTaskStack, false /* notifyStackChanges */); 1124 } 1125 1126 /**** OnAnimationFinishedListener Implementation ****/ 1127 1128 @Override onAnimationFinished()1129 public void onAnimationFinished() { 1130 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent()); 1131 } 1132 } 1133