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