2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recents;
19 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
20 import static android.view.View.MeasureSpec;
22 import android.app.ActivityManager;
23 import android.app.ActivityOptions;
24 import android.content.ActivityNotFoundException;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.graphics.Bitmap;
29 import android.graphics.Canvas;
30 import android.graphics.Rect;
31 import android.graphics.RectF;
32 import android.graphics.drawable.Drawable;
33 import android.os.Handler;
34 import android.os.SystemClock;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.util.MutableBoolean;
38 import android.view.AppTransitionAnimationSpec;
39 import android.view.LayoutInflater;
40 import android.view.ViewConfiguration;
41 import android.view.WindowManager;
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.policy.DockedDividerUtils;
45 import com.android.systemui.R;
46 import com.android.systemui.SystemUIApplication;
47 import com.android.systemui.recents.events.EventBus;
48 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
49 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
50 import com.android.systemui.recents.events.activity.HideRecentsEvent;
51 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
52 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
53 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
54 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
55 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
56 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
57 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
58 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
59 import com.android.systemui.recents.misc.DozeTrigger;
60 import com.android.systemui.recents.misc.ForegroundThread;
61 import com.android.systemui.recents.misc.SystemServicesProxy;
62 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
63 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
64 import com.android.systemui.recents.model.RecentsTaskLoader;
65 import com.android.systemui.recents.model.Task;
66 import com.android.systemui.recents.model.TaskGrouping;
67 import com.android.systemui.recents.model.TaskStack;
68 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
69 import com.android.systemui.recents.views.TaskStackView;
70 import com.android.systemui.recents.views.TaskStackViewScroller;
71 import com.android.systemui.recents.views.TaskViewHeader;
72 import com.android.systemui.recents.views.TaskViewTransform;
73 import com.android.systemui.stackdivider.DividerView;
74 import com.android.systemui.statusbar.BaseStatusBar;
75 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
76 import com.android.systemui.statusbar.phone.PhoneStatusBar;
78 import java.util.ArrayList;
81 * An implementation of the Recents component for the current user. For secondary users, this can
82 * be called remotely from the system user.
84 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
86 private final static String TAG = "RecentsImpl";
88 // The minimum amount of time between each recents button press that we will handle
89 private final static int MIN_TOGGLE_DELAY_MS = 350;
91 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
92 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
93 // duration, then we will toggle recents after this duration.
94 private final static int FAST_ALT_TAB_DELAY_MS = 225;
96 public final static String RECENTS_PACKAGE = "com.android.systemui";
97 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
100 * An implementation of TaskStackListener, that allows us to listen for changes to the system
101 * task stacks and update recents accordingly.
103 class TaskStackListenerImpl extends TaskStackListener {
105 public void onTaskStackChanged() {
106 // Preloads the next task
107 RecentsConfiguration config = Recents.getConfiguration();
108 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
109 RecentsTaskLoader loader = Recents.getTaskLoader();
110 SystemServicesProxy ssp = Recents.getSystemServices();
111 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
113 // Load the next task only if we aren't svelte
114 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
115 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
116 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
117 // This callback is made when a new activity is launched and the old one is paused
118 // so ignore the current activity and try and preload the thumbnail for the
120 if (runningTaskInfo != null) {
121 launchOpts.runningTaskId = runningTaskInfo.id;
123 launchOpts.numVisibleTasks = 2;
124 launchOpts.numVisibleTaskThumbnails = 2;
125 launchOpts.onlyLoadForCache = true;
126 launchOpts.onlyLoadPausedActivities = true;
127 loader.loadTasks(mContext, plan, launchOpts);
132 protected static RecentsTaskLoadPlan sInstanceLoadPlan;
134 protected Context mContext;
135 protected Handler mHandler;
136 TaskStackListenerImpl mTaskStackListener;
137 boolean mDraggingInRecents;
138 boolean mLaunchedWhileDocking;
141 Rect mTaskStackBounds = new Rect();
142 TaskViewTransform mTmpTransform = new TaskViewTransform();
143 int mStatusBarHeight;
148 // Header (for transition)
149 TaskViewHeader mHeaderBar;
150 final Object mHeaderBarLock = new Object();
151 protected TaskStackView mDummyStackView;
153 // Variables to keep track of if we need to start recents after binding
154 protected boolean mTriggeredFromAltTab;
155 protected long mLastToggleTime;
156 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
159 // When this fires, then the user has not released alt-tab for at least
160 // FAST_ALT_TAB_DELAY_MS milliseconds
161 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
162 false /* reloadTasks */, false /* fromHome */,
163 DividerView.INVALID_RECENTS_GROW_TARGET);
167 protected Bitmap mThumbTransitionBitmapCache;
169 public RecentsImpl(Context context) {
171 mHandler = new Handler();
173 // Initialize the static foreground thread
174 ForegroundThread.get();
176 // Register the task stack listener
177 mTaskStackListener = new TaskStackListenerImpl();
178 SystemServicesProxy ssp = Recents.getSystemServices();
179 ssp.registerTaskStackListener(mTaskStackListener);
181 // Initialize the static configuration resources
182 LayoutInflater inflater = LayoutInflater.from(mContext);
183 mDummyStackView = new TaskStackView(mContext);
184 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
189 public void onBootCompleted() {
190 // When we start, preload the data associated with the previous recent tasks.
191 // We can use a new plan since the caches will be the same.
192 RecentsTaskLoader loader = Recents.getTaskLoader();
193 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
194 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
195 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
196 launchOpts.numVisibleTasks = loader.getIconCacheSize();
197 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
198 launchOpts.onlyLoadForCache = true;
199 loader.loadTasks(mContext, plan, launchOpts);
202 public void onConfigurationChanged() {
204 mDummyStackView.reloadOnConfigurationChange();
205 mHeaderBar.onConfigurationChanged();
209 * This is only called from the system user's Recents. Secondary users will instead proxy their
210 * visibility change events through to the system user via
211 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
213 public void onVisibilityChanged(Context context, boolean visible) {
214 SystemUIApplication app = (SystemUIApplication) context;
215 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
216 if (statusBar != null) {
217 statusBar.updateRecentsVisibility(visible);
222 * This is only called from the system user's Recents. Secondary users will instead proxy their
223 * visibility change events through to the system user via
224 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
226 public void onStartScreenPinning(Context context, int taskId) {
227 SystemUIApplication app = (SystemUIApplication) context;
228 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
229 if (statusBar != null) {
230 statusBar.showScreenPinningRequest(taskId, false);
234 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
235 boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
237 mTriggeredFromAltTab = triggeredFromAltTab;
238 mDraggingInRecents = draggingInRecents;
239 mLaunchedWhileDocking = launchedWhileDockingTask;
240 if (mFastAltTabTrigger.isAsleep()) {
241 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
242 mFastAltTabTrigger.stopDozing();
243 } else if (mFastAltTabTrigger.isDozing()) {
244 // Fast alt-tab duration has not elapsed. If this is triggered by a different
245 // showRecents() call, then ignore that call for now.
246 // TODO: We can not handle quick tabs that happen between the initial showRecents() call
247 // that started the activity and the activity starting up. The severity of this
248 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
249 if (!triggeredFromAltTab) {
252 mFastAltTabTrigger.stopDozing();
253 } else if (triggeredFromAltTab) {
254 // The fast alt-tab detector is not yet running, so start the trigger and wait for the
255 // hideRecents() call, or for the fast alt-tab duration to elapse
256 mFastAltTabTrigger.startDozing();
261 // Check if the top task is in the home stack, and start the recents activity
262 SystemServicesProxy ssp = Recents.getSystemServices();
263 boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
264 MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
265 if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
266 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
267 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
270 } catch (ActivityNotFoundException e) {
271 Log.e(TAG, "Failed to launch RecentsActivity", e);
275 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
276 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
277 // The user has released alt-tab before the trigger has run, so just show the next
281 // Cancel the fast alt-tab trigger
282 mFastAltTabTrigger.stopDozing();
286 // Defer to the activity to handle hiding recents, if it handles it, then it must still
288 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
289 triggeredFromHomeKey));
292 public void toggleRecents(int growTarget) {
293 // Skip this toggle if we are already waiting to trigger recents via alt-tab
294 if (mFastAltTabTrigger.isDozing()) {
298 mDraggingInRecents = false;
299 mLaunchedWhileDocking = false;
300 mTriggeredFromAltTab = false;
303 SystemServicesProxy ssp = Recents.getSystemServices();
304 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
305 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
307 if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
308 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
309 RecentsConfiguration config = Recents.getConfiguration();
310 RecentsActivityLaunchState launchState = config.getLaunchState();
311 if (!launchState.launchedWithAltTab) {
312 // If the user taps quickly
313 if (!debugFlags.isPagingEnabled() ||
314 (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
315 elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
316 // Launch the next focused task
317 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
319 // Notify recents to move onto the next task
320 EventBus.getDefault().post(new IterateRecentsEvent());
323 // If the user has toggled it too quickly, then just eat up the event here (it's
324 // better than showing a janky screenshot).
325 // NOTE: Ideally, the screenshot mechanism would take the window transform into
327 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
331 EventBus.getDefault().post(new ToggleRecentsEvent());
332 mLastToggleTime = SystemClock.elapsedRealtime();
336 // If the user has toggled it too quickly, then just eat up the event here (it's
337 // better than showing a janky screenshot).
338 // NOTE: Ideally, the screenshot mechanism would take the window transform into
340 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
344 // Otherwise, start the recents activity
345 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
346 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
349 // Only close the other system windows if we are actually showing recents
350 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
351 mLastToggleTime = SystemClock.elapsedRealtime();
353 } catch (ActivityNotFoundException e) {
354 Log.e(TAG, "Failed to launch RecentsActivity", e);
358 public void preloadRecents() {
359 // Preload only the raw task list into a new load plan (which will be consumed by the
360 // RecentsActivity) only if there is a task to animate to.
361 SystemServicesProxy ssp = Recents.getSystemServices();
362 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
363 if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
364 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
365 RecentsTaskLoader loader = Recents.getTaskLoader();
366 sInstanceLoadPlan = loader.createLoadPlan(mContext);
367 sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
368 loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
369 TaskStack stack = sInstanceLoadPlan.getTaskStack();
370 if (stack.getTaskCount() > 0) {
371 // Only preload the icon (but not the thumbnail since it may not have been taken for
372 // the pausing activity)
373 preloadIcon(runningTask.id);
375 // At this point, we don't know anything about the stack state. So only calculate
376 // the dimensions of the thumbnail that we need for the transition into Recents, but
377 // do not draw it until we construct the activity options when we start Recents
378 updateHeaderBarLayout(stack, null /* window rect override*/);
383 public void cancelPreloadingRecents() {
387 public void onDraggingInRecents(float distanceFromTop) {
388 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
391 public void onDraggingInRecentsEnded(float velocity) {
392 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
396 * Transitions to the next recent task in the stack.
398 public void showNextTask() {
399 SystemServicesProxy ssp = Recents.getSystemServices();
400 RecentsTaskLoader loader = Recents.getTaskLoader();
401 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
402 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
403 TaskStack focusedStack = plan.getTaskStack();
405 // Return early if there are no tasks in the focused stack
406 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
408 // Return early if there is no running task
409 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
410 if (runningTask == null) return;
412 // Find the task in the recents list
413 boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
414 ArrayList<Task> tasks = focusedStack.getStackTasks();
416 ActivityOptions launchOpts = null;
417 int taskCount = tasks.size();
418 for (int i = taskCount - 1; i >= 1; i--) {
419 Task task = tasks.get(i);
420 if (isRunningTaskInHomeStack) {
421 toTask = tasks.get(i - 1);
422 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
423 R.anim.recents_launch_next_affiliated_task_target,
424 R.anim.recents_fast_toggle_app_home_exit);
426 } else if (task.key.id == runningTask.id) {
427 toTask = tasks.get(i - 1);
428 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
429 R.anim.recents_launch_prev_affiliated_task_target,
430 R.anim.recents_launch_prev_affiliated_task_source);
435 // Return early if there is no next task
436 if (toTask == null) {
437 ssp.startInPlaceAnimationOnFrontMostApplication(
438 ActivityOptions.makeCustomInPlaceAnimation(mContext,
439 R.anim.recents_launch_prev_affiliated_task_bounce));
444 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
448 * Transitions to the next affiliated task.
450 public void showRelativeAffiliatedTask(boolean showNextTask) {
451 SystemServicesProxy ssp = Recents.getSystemServices();
452 RecentsTaskLoader loader = Recents.getTaskLoader();
453 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
454 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
455 TaskStack focusedStack = plan.getTaskStack();
457 // Return early if there are no tasks in the focused stack
458 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
460 // Return early if there is no running task (can't determine affiliated tasks in this case)
461 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
462 if (runningTask == null) return;
463 // Return early if the running task is in the home stack (optimization)
464 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
466 // Find the task in the recents list
467 ArrayList<Task> tasks = focusedStack.getStackTasks();
469 ActivityOptions launchOpts = null;
470 int taskCount = tasks.size();
471 int numAffiliatedTasks = 0;
472 for (int i = 0; i < taskCount; i++) {
473 Task task = tasks.get(i);
474 if (task.key.id == runningTask.id) {
475 TaskGrouping group = task.group;
476 Task.TaskKey toTaskKey;
478 toTaskKey = group.getNextTaskInGroup(task);
479 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
480 R.anim.recents_launch_next_affiliated_task_target,
481 R.anim.recents_launch_next_affiliated_task_source);
483 toTaskKey = group.getPrevTaskInGroup(task);
484 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
485 R.anim.recents_launch_prev_affiliated_task_target,
486 R.anim.recents_launch_prev_affiliated_task_source);
488 if (toTaskKey != null) {
489 toTask = focusedStack.findTaskWithId(toTaskKey.id);
491 numAffiliatedTasks = group.getTaskCount();
496 // Return early if there is no next task
497 if (toTask == null) {
498 if (numAffiliatedTasks > 1) {
500 ssp.startInPlaceAnimationOnFrontMostApplication(
501 ActivityOptions.makeCustomInPlaceAnimation(mContext,
502 R.anim.recents_launch_next_affiliated_task_bounce));
504 ssp.startInPlaceAnimationOnFrontMostApplication(
505 ActivityOptions.makeCustomInPlaceAnimation(mContext,
506 R.anim.recents_launch_prev_affiliated_task_bounce));
512 // Keep track of actually launched affiliated tasks
513 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
516 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
519 public void showNextAffiliatedTask() {
520 // Keep track of when the affiliated task is triggered
521 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
522 showRelativeAffiliatedTask(true);
525 public void showPrevAffiliatedTask() {
526 // Keep track of when the affiliated task is triggered
527 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
528 showRelativeAffiliatedTask(false);
531 public void dockTopTask(int topTaskId, int dragMode,
532 int stackCreateMode, Rect initialBounds) {
533 SystemServicesProxy ssp = Recents.getSystemServices();
535 // Make sure we inform DividerView before we actually start the activity so we can change
536 // the resize mode already.
537 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
538 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
540 false /* triggeredFromAltTab */,
541 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
543 true /* launchedWhileDockingTask*/,
544 false /* fromHome */,
545 DividerView.INVALID_RECENTS_GROW_TARGET);
550 * Returns the preloaded load plan and invalidates it.
552 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
553 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
554 sInstanceLoadPlan = null;
559 * Reloads all the resources for the current configuration.
561 private void reloadResources() {
562 Resources res = mContext.getResources();
564 mStatusBarHeight = res.getDimensionPixelSize(
565 com.android.internal.R.dimen.status_bar_height);
566 mNavBarHeight = res.getDimensionPixelSize(
567 com.android.internal.R.dimen.navigation_bar_height);
568 mNavBarWidth = res.getDimensionPixelSize(
569 com.android.internal.R.dimen.navigation_bar_width);
570 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
571 R.dimen.recents_task_view_header_height,
572 R.dimen.recents_task_view_header_height,
573 R.dimen.recents_task_view_header_height,
574 R.dimen.recents_task_view_header_height_tablet_land,
575 R.dimen.recents_task_view_header_height,
576 R.dimen.recents_task_view_header_height_tablet_land);
580 * Prepares the header bar layout for the next transition, if the task view bounds has changed
581 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
583 * @param stack the stack to initialize the stack layout with
584 * @param windowRectOverride the rectangle to use when calculating the stack state which can
585 * be different from the current window rect if recents is resizing
586 * while being launched
588 private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
589 SystemServicesProxy ssp = Recents.getSystemServices();
590 Rect displayRect = ssp.getDisplayRect();
591 Rect systemInsets = new Rect();
592 ssp.getStableInsets(systemInsets);
593 Rect windowRect = windowRectOverride != null
594 ? new Rect(windowRectOverride)
595 : ssp.getWindowRect();
596 // When docked, the nav bar insets are consumed and the activity is measured without insets.
597 // However, the window bounds include the insets, so we need to subtract them here to make
599 if (ssp.hasDockedTask()) {
600 windowRect.bottom -= systemInsets.bottom;
601 systemInsets.bottom = 0;
603 calculateWindowStableInsets(systemInsets, windowRect);
604 windowRect.offsetTo(0, 0);
606 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
608 // Rebind the header bar and draw it for the transition
609 stackLayout.setSystemInsets(systemInsets);
611 stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
612 systemInsets.left, systemInsets.right, mTaskStackBounds);
614 stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
615 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
616 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
618 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
619 if (!taskViewBounds.isEmpty()) {
620 int taskViewWidth = taskViewBounds.width();
621 synchronized (mHeaderBarLock) {
622 if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
623 mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
625 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
626 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
628 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
631 // Update the transition bitmap to match the new header bar height
632 if (mThumbTransitionBitmapCache == null ||
633 (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
634 (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
635 mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
636 mTaskBarHeight, Bitmap.Config.ARGB_8888);
643 * Given the stable insets and the rect for our window, calculates the insets that affect our
646 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
647 Rect displayRect = Recents.getSystemServices().getDisplayRect();
649 // Display rect without insets - available app space
650 Rect appRect = new Rect(displayRect);
651 appRect.inset(inOutInsets);
653 // Our window intersected with available app space
654 Rect windowRectWithInsets = new Rect(windowRect);
655 windowRectWithInsets.intersect(appRect);
656 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
657 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
658 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
659 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
663 * Preloads the icon of a task.
665 private void preloadIcon(int runningTaskId) {
666 // Ensure that we load the running task's icon
667 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
668 launchOpts.runningTaskId = runningTaskId;
669 launchOpts.loadThumbnails = false;
670 launchOpts.onlyLoadForCache = true;
671 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
675 * Creates the activity options for a unknown state->recents transition.
677 protected ActivityOptions getUnknownTransitionActivityOptions() {
678 return ActivityOptions.makeCustomAnimation(mContext,
679 R.anim.recents_from_unknown_enter,
680 R.anim.recents_from_unknown_exit,
685 * Creates the activity options for a home->recents transition.
687 protected ActivityOptions getHomeTransitionActivityOptions() {
688 return ActivityOptions.makeCustomAnimation(mContext,
689 R.anim.recents_from_launcher_enter,
690 R.anim.recents_from_launcher_exit,
695 * Creates the activity options for an app->recents transition.
697 private ActivityOptions getThumbnailTransitionActivityOptions(
698 ActivityManager.RunningTaskInfo runningTask, TaskStackView stackView,
699 Rect windowOverrideRect) {
700 if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
701 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
702 ArrayList<Task> tasks = stackView.getStack().getStackTasks();
703 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
704 TaskStackViewScroller stackScroller = stackView.getScroller();
706 stackView.updateLayoutAlgorithm(true /* boundScroll */);
707 stackView.updateToInitialState();
709 for (int i = tasks.size() - 1; i >= 0; i--) {
710 Task task = tasks.get(i);
711 if (task.isFreeformTask()) {
712 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
713 stackScroller.getStackScroll(), mTmpTransform, null,
715 Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
716 mThumbTransitionBitmapCache);
717 Rect toTaskRect = new Rect();
718 mTmpTransform.rect.round(toTaskRect);
719 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
722 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
723 specs.toArray(specsArray);
724 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
725 specsArray, mHandler, null, this);
727 // Update the destination rect
728 Task toTask = new Task();
729 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
731 Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
732 mThumbTransitionBitmapCache);
733 if (thumbnail != null) {
734 RectF toTaskRect = toTransform.rect;
735 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
736 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
737 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
739 // If both the screenshot and thumbnail fails, then just fall back to the default transition
740 return getUnknownTransitionActivityOptions();
745 * Returns the transition rect for the given task id.
747 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
748 Task runningTaskOut, Rect windowOverrideRect) {
749 // Find the running task in the TaskStack
750 TaskStack stack = stackView.getStack();
751 Task launchTask = stack.getLaunchTarget();
752 if (launchTask != null) {
753 runningTaskOut.copyFrom(launchTask);
755 // If no task is specified or we can not find the task just use the front most one
756 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
757 runningTaskOut.copyFrom(launchTask);
760 // Get the transform for the running task
761 stackView.updateLayoutAlgorithm(true /* boundScroll */);
762 stackView.updateToInitialState();
763 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
764 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
765 return mTmpTransform;
769 * Draws the header of a task used for the window animation into a bitmap.
771 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
773 SystemServicesProxy ssp = Recents.getSystemServices();
774 if (toTransform != null && toTask.key != null) {
775 synchronized (mHeaderBarLock) {
776 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
777 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
778 (int) toTransform.rect.height());
779 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
780 thumbnail.eraseColor(0xFFff0000);
782 thumbnail.eraseColor(0);
783 Canvas c = new Canvas(thumbnail);
784 // Workaround for b/27815919, reset the callback so that we do not trigger an
785 // invalidate on the header bar as a result of updating the icon
786 Drawable icon = mHeaderBar.getIconView().getDrawable();
788 icon.setCallback(null);
790 mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
792 mHeaderBar.onTaskDataLoaded();
793 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
798 return thumbnail.createAshmemBitmap();
804 * Shows the recents activity
806 protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
807 boolean isHomeStackVisible, boolean animate, int growTarget) {
808 RecentsTaskLoader loader = Recents.getTaskLoader();
809 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
810 SystemServicesProxy ssp = Recents.getSystemServices();
811 boolean isBlacklisted = (runningTask != null)
812 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
815 int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
819 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
820 // should always preload the tasks now. If we are dragging in recents, reload them as
821 // the stacks might have changed.
822 if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
823 // Create a new load plan if preloadRecents() was never triggered
824 sInstanceLoadPlan = loader.createLoadPlan(mContext);
826 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
827 loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
830 TaskStack stack = sInstanceLoadPlan.getTaskStack();
831 boolean hasRecentTasks = stack.getTaskCount() > 0;
832 boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
835 // Update the launch state that we need in updateHeaderBarLayout()
836 launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
837 launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
838 launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
839 launchState.launchedViaDockGesture = mLaunchedWhileDocking;
840 launchState.launchedViaDragGesture = mDraggingInRecents;
841 launchState.launchedToTaskId = runningTaskId;
842 launchState.launchedWithAltTab = mTriggeredFromAltTab;
844 // Preload the icon (this will be a null-op if we have preloaded the icon already in
846 preloadIcon(runningTaskId);
848 // Update the header bar if necessary
849 Rect windowOverrideRect = getWindowRectOverride(growTarget);
850 updateHeaderBarLayout(stack, windowOverrideRect);
852 // Prepare the dummy stack for the transition
853 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
854 mDummyStackView.computeStackVisibilityReport();
856 // Update the remaining launch state
857 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
858 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
861 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
865 ActivityOptions opts;
867 opts = getUnknownTransitionActivityOptions();
868 } else if (useThumbnailTransition) {
869 // Try starting with a thumbnail transition
870 opts = getThumbnailTransitionActivityOptions(runningTask, mDummyStackView,
873 // If there is no thumbnail transition, but is launching from home into recents, then
874 // use a quick home transition
875 opts = hasRecentTasks
876 ? getHomeTransitionActivityOptions()
877 : getUnknownTransitionActivityOptions();
879 startRecentsActivity(opts);
880 mLastToggleTime = SystemClock.elapsedRealtime();
883 private Rect getWindowRectOverride(int growTarget) {
884 if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
887 Rect result = new Rect();
888 Rect displayRect = Recents.getSystemServices().getDisplayRect();
889 DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
890 result, displayRect.width(), displayRect.height(),
891 Recents.getSystemServices().getDockedDividerSize(mContext));
896 * Starts the recents activity.
898 private void startRecentsActivity(ActivityOptions opts) {
899 Intent intent = new Intent();
900 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
901 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
902 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
903 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
906 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
908 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
910 EventBus.getDefault().send(new RecentsActivityStartingEvent());
913 /**** OnAnimationFinishedListener Implementation ****/
916 public void onAnimationFinished() {
917 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());