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;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.appwidget.AppWidgetProviderInfo;
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.os.Handler;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.util.Log;
36 import android.util.MutableBoolean;
37 import android.view.AppTransitionAnimationSpec;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewConfiguration;
42 import com.android.internal.logging.MetricsLogger;
43 import com.android.systemui.Prefs;
44 import com.android.systemui.R;
45 import com.android.systemui.SystemUIApplication;
46 import com.android.systemui.recents.events.EventBus;
47 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
48 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
49 import com.android.systemui.recents.events.activity.HideRecentsEvent;
50 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
51 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
52 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
53 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
54 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
55 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
56 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
57 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
58 import com.android.systemui.recents.misc.DozeTrigger;
59 import com.android.systemui.recents.misc.ForegroundThread;
60 import com.android.systemui.recents.misc.SystemServicesProxy;
61 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
62 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
63 import com.android.systemui.recents.model.RecentsTaskLoader;
64 import com.android.systemui.recents.model.Task;
65 import com.android.systemui.recents.model.TaskGrouping;
66 import com.android.systemui.recents.model.TaskStack;
67 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
68 import com.android.systemui.recents.views.TaskStackView;
69 import com.android.systemui.recents.views.TaskStackViewScroller;
70 import com.android.systemui.recents.views.TaskViewHeader;
71 import com.android.systemui.recents.views.TaskViewTransform;
72 import com.android.systemui.statusbar.BaseStatusBar;
73 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
74 import com.android.systemui.statusbar.phone.PhoneStatusBar;
76 import java.util.ArrayList;
79 * An implementation of the Recents component for the current user. For secondary users, this can
80 * be called remotely from the system user.
82 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
84 private final static String TAG = "RecentsImpl";
86 // The minimum amount of time between each recents button press that we will handle
87 private final static int MIN_TOGGLE_DELAY_MS = 350;
89 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
90 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
91 // duration, then we will toggle recents after this duration.
92 private final static int FAST_ALT_TAB_DELAY_MS = 225;
94 public final static String RECENTS_PACKAGE = "com.android.systemui";
95 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
98 * An implementation of TaskStackListener, that allows us to listen for changes to the system
99 * task stacks and update recents accordingly.
101 class TaskStackListenerImpl extends TaskStackListener {
103 public void onTaskStackChanged() {
104 // Preloads the next task
105 RecentsConfiguration config = Recents.getConfiguration();
106 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
107 RecentsTaskLoader loader = Recents.getTaskLoader();
108 SystemServicesProxy ssp = Recents.getSystemServices();
109 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
111 // Load the next task only if we aren't svelte
112 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
113 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
114 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
115 // This callback is made when a new activity is launched and the old one is paused
116 // so ignore the current activity and try and preload the thumbnail for the
118 if (runningTaskInfo != null) {
119 launchOpts.runningTaskId = runningTaskInfo.id;
121 launchOpts.numVisibleTasks = 2;
122 launchOpts.numVisibleTaskThumbnails = 2;
123 launchOpts.onlyLoadForCache = true;
124 launchOpts.onlyLoadPausedActivities = true;
125 loader.loadTasks(mContext, plan, launchOpts);
130 protected static RecentsTaskLoadPlan sInstanceLoadPlan;
132 protected Context mContext;
133 protected Handler mHandler;
134 TaskStackListenerImpl mTaskStackListener;
135 RecentsAppWidgetHost mAppWidgetHost;
136 protected boolean mCanReuseTaskStackViews = true;
137 boolean mDraggingInRecents;
138 boolean mLaunchedWhileDocking;
141 Rect mSearchBarBounds = new Rect();
142 Rect mTaskStackBounds = new Rect();
143 Rect mLastTaskViewBounds = new Rect();
144 TaskViewTransform mTmpTransform = new TaskViewTransform();
145 int mStatusBarHeight;
150 // Header (for transition)
151 TaskViewHeader mHeaderBar;
152 final Object mHeaderBarLock = new Object();
153 protected TaskStackView mDummyStackView;
155 // Variables to keep track of if we need to start recents after binding
156 protected boolean mTriggeredFromAltTab;
157 protected long mLastToggleTime;
158 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
161 // When this fires, then the user has not released alt-tab for at least
162 // FAST_ALT_TAB_DELAY_MS milliseconds
163 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
164 false /* reloadTasks */);
168 protected Bitmap mThumbnailTransitionBitmapCache;
169 Task mThumbnailTransitionBitmapCacheKey;
171 public RecentsImpl(Context context) {
173 mHandler = new Handler();
174 mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
176 // Initialize the static foreground thread
177 ForegroundThread.get();
179 // Register the task stack listener
180 mTaskStackListener = new TaskStackListenerImpl();
181 SystemServicesProxy ssp = Recents.getSystemServices();
182 ssp.registerTaskStackListener(mTaskStackListener);
184 // Initialize the static configuration resources
185 reloadHeaderBarLayout();
186 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
188 // When we start, preload the data associated with the previous recent tasks.
189 // We can use a new plan since the caches will be the same.
190 RecentsTaskLoader loader = Recents.getTaskLoader();
191 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
192 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
193 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
194 launchOpts.numVisibleTasks = loader.getIconCacheSize();
195 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
196 launchOpts.onlyLoadForCache = true;
197 loader.loadTasks(mContext, plan, launchOpts);
200 public void onBootCompleted() {
201 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
204 public void onConfigurationChanged() {
205 reloadHeaderBarLayout();
206 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
207 // Don't reuse task stack views if the configuration changes
208 mCanReuseTaskStackViews = false;
209 Recents.getConfiguration().updateOnConfigurationChange();
213 * This is only called from the system user's Recents. Secondary users will instead proxy their
214 * visibility change events through to the system user via
215 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
217 public void onVisibilityChanged(Context context, boolean visible) {
218 SystemUIApplication app = (SystemUIApplication) context;
219 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
220 if (statusBar != null) {
221 statusBar.updateRecentsVisibility(visible);
226 * This is only called from the system user's Recents. Secondary users will instead proxy their
227 * visibility change events through to the system user via
228 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
230 public void onStartScreenPinning(Context context) {
231 SystemUIApplication app = (SystemUIApplication) context;
232 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
233 if (statusBar != null) {
234 statusBar.showScreenPinningRequest(false);
238 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
239 boolean animate, boolean launchedWhileDockingTask) {
240 mTriggeredFromAltTab = triggeredFromAltTab;
241 mDraggingInRecents = draggingInRecents;
242 mLaunchedWhileDocking = launchedWhileDockingTask;
243 if (mFastAltTabTrigger.isAsleep()) {
244 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
245 mFastAltTabTrigger.stopDozing();
246 } else if (mFastAltTabTrigger.isDozing()) {
247 // Fast alt-tab duration has not elapsed. If this is triggered by a different
248 // showRecents() call, then ignore that call for now.
249 // TODO: We can not handle quick tabs that happen between the initial showRecents() call
250 // that started the activity and the activity starting up. The severity of this
251 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
252 if (!triggeredFromAltTab) {
255 mFastAltTabTrigger.stopDozing();
256 } else if (triggeredFromAltTab) {
257 // The fast alt-tab detector is not yet running, so start the trigger and wait for the
258 // hideRecents() call, or for the fast alt-tab duration to elapse
259 mFastAltTabTrigger.startDozing();
264 // Check if the top task is in the home stack, and start the recents activity
265 SystemServicesProxy ssp = Recents.getSystemServices();
266 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
267 MutableBoolean isTopTaskHome = new MutableBoolean(true);
268 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
269 startRecentsActivity(topTask, isTopTaskHome.value, animate);
271 } catch (ActivityNotFoundException e) {
272 Log.e(TAG, "Failed to launch RecentsActivity", e);
276 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
277 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
278 // The user has released alt-tab before the trigger has run, so just show the next
282 // Cancel the fast alt-tab trigger
283 mFastAltTabTrigger.stopDozing();
287 // Defer to the activity to handle hiding recents, if it handles it, then it must still
289 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
290 triggeredFromHomeKey));
293 public void toggleRecents() {
294 // Skip this toggle if we are already waiting to trigger recents via alt-tab
295 if (mFastAltTabTrigger.isDozing()) {
299 mDraggingInRecents = false;
300 mLaunchedWhileDocking = false;
301 mTriggeredFromAltTab = false;
304 SystemServicesProxy ssp = Recents.getSystemServices();
305 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
306 MutableBoolean isTopTaskHome = new MutableBoolean(true);
307 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
309 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
310 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
311 RecentsConfiguration config = Recents.getConfiguration();
312 RecentsActivityLaunchState launchState = config.getLaunchState();
313 if (!launchState.launchedWithAltTab) {
314 // If the user taps quickly
315 if (!debugFlags.isPagingEnabled() ||
316 (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
317 elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
318 // Launch the next focused task
319 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
321 // Notify recents to move onto the next task
322 EventBus.getDefault().post(new IterateRecentsEvent());
325 // If the user has toggled it too quickly, then just eat up the event here (it's
326 // better than showing a janky screenshot).
327 // NOTE: Ideally, the screenshot mechanism would take the window transform into
329 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
333 EventBus.getDefault().post(new ToggleRecentsEvent());
334 mLastToggleTime = SystemClock.elapsedRealtime();
338 // If the user has toggled it too quickly, then just eat up the event here (it's
339 // better than showing a janky screenshot).
340 // NOTE: Ideally, the screenshot mechanism would take the window transform into
342 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
346 // Otherwise, start the recents activity
347 startRecentsActivity(topTask, isTopTaskHome.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 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
363 MutableBoolean topTaskHome = new MutableBoolean(true);
364 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
365 RecentsTaskLoader loader = Recents.getTaskLoader();
366 sInstanceLoadPlan = loader.createLoadPlan(mContext);
367 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
368 loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
369 TaskStack stack = sInstanceLoadPlan.getTaskStack();
370 if (stack.getTaskCount() > 0) {
371 // We try and draw the thumbnail transition bitmap in parallel before
372 // toggle/show recents is called
373 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
378 public void cancelPreloadingRecents() {
382 public void onDraggingInRecents(float distanceFromTop) {
383 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
386 public void onDraggingInRecentsEnded(float velocity) {
387 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
391 * Transitions to the next recent task in the stack.
393 public void showNextTask() {
394 SystemServicesProxy ssp = Recents.getSystemServices();
395 RecentsTaskLoader loader = Recents.getTaskLoader();
396 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
397 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
398 TaskStack focusedStack = plan.getTaskStack();
400 // Return early if there are no tasks in the focused stack
401 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
403 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
404 // Return early if there is no running task
405 if (runningTask == null) return;
407 // Find the task in the recents list
408 boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
409 ArrayList<Task> tasks = focusedStack.getStackTasks();
411 ActivityOptions launchOpts = null;
412 int taskCount = tasks.size();
413 for (int i = taskCount - 1; i >= 1; i--) {
414 Task task = tasks.get(i);
416 toTask = tasks.get(i - 1);
417 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
418 R.anim.recents_launch_next_affiliated_task_target,
419 R.anim.recents_fast_toggle_app_home_exit);
421 } else if (task.key.id == runningTask.id) {
422 toTask = tasks.get(i - 1);
423 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
424 R.anim.recents_launch_prev_affiliated_task_target,
425 R.anim.recents_launch_prev_affiliated_task_source);
430 // Return early if there is no next task
431 if (toTask == null) {
432 ssp.startInPlaceAnimationOnFrontMostApplication(
433 ActivityOptions.makeCustomInPlaceAnimation(mContext,
434 R.anim.recents_launch_prev_affiliated_task_bounce));
439 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
443 * Transitions to the next affiliated task.
445 public void showRelativeAffiliatedTask(boolean showNextTask) {
446 SystemServicesProxy ssp = Recents.getSystemServices();
447 RecentsTaskLoader loader = Recents.getTaskLoader();
448 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
449 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
450 TaskStack focusedStack = plan.getTaskStack();
452 // Return early if there are no tasks in the focused stack
453 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
455 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
456 // Return early if there is no running task (can't determine affiliated tasks in this case)
457 if (runningTask == null) return;
458 // Return early if the running task is in the home stack (optimization)
459 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
461 // Find the task in the recents list
462 ArrayList<Task> tasks = focusedStack.getStackTasks();
464 ActivityOptions launchOpts = null;
465 int taskCount = tasks.size();
466 int numAffiliatedTasks = 0;
467 for (int i = 0; i < taskCount; i++) {
468 Task task = tasks.get(i);
469 if (task.key.id == runningTask.id) {
470 TaskGrouping group = task.group;
471 Task.TaskKey toTaskKey;
473 toTaskKey = group.getNextTaskInGroup(task);
474 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
475 R.anim.recents_launch_next_affiliated_task_target,
476 R.anim.recents_launch_next_affiliated_task_source);
478 toTaskKey = group.getPrevTaskInGroup(task);
479 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
480 R.anim.recents_launch_prev_affiliated_task_target,
481 R.anim.recents_launch_prev_affiliated_task_source);
483 if (toTaskKey != null) {
484 toTask = focusedStack.findTaskWithId(toTaskKey.id);
486 numAffiliatedTasks = group.getTaskCount();
491 // Return early if there is no next task
492 if (toTask == null) {
493 if (numAffiliatedTasks > 1) {
495 ssp.startInPlaceAnimationOnFrontMostApplication(
496 ActivityOptions.makeCustomInPlaceAnimation(mContext,
497 R.anim.recents_launch_next_affiliated_task_bounce));
499 ssp.startInPlaceAnimationOnFrontMostApplication(
500 ActivityOptions.makeCustomInPlaceAnimation(mContext,
501 R.anim.recents_launch_prev_affiliated_task_bounce));
507 // Keep track of actually launched affiliated tasks
508 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
511 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
514 public void showNextAffiliatedTask() {
515 // Keep track of when the affiliated task is triggered
516 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
517 showRelativeAffiliatedTask(true);
520 public void showPrevAffiliatedTask() {
521 // Keep track of when the affiliated task is triggered
522 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
523 showRelativeAffiliatedTask(false);
526 public void dockTopTask(int topTaskId, int dragMode,
527 int stackCreateMode, Rect initialBounds) {
528 SystemServicesProxy ssp = Recents.getSystemServices();
530 // Make sure we inform DividerView before we actually start the activity so we can change
531 // the resize mode already.
532 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
533 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
535 false /* triggeredFromAltTab */,
536 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
538 true /* launchedWhileDockingTask*/);
543 * Returns the preloaded load plan and invalidates it.
545 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
546 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
547 sInstanceLoadPlan = null;
552 * Reloads all the layouts for the header bar transition.
554 private void reloadHeaderBarLayout() {
555 Resources res = mContext.getResources();
556 LayoutInflater inflater = LayoutInflater.from(mContext);
558 mStatusBarHeight = res.getDimensionPixelSize(
559 com.android.internal.R.dimen.status_bar_height);
560 mNavBarHeight = res.getDimensionPixelSize(
561 com.android.internal.R.dimen.navigation_bar_height);
562 mNavBarWidth = res.getDimensionPixelSize(
563 com.android.internal.R.dimen.navigation_bar_width);
564 mTaskBarHeight = res.getDimensionPixelSize(
565 R.dimen.recents_task_bar_height);
566 mDummyStackView = new TaskStackView(mContext);
567 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
572 * Prepares the header bar layout for the next transition, if the task view bounds has changed
573 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
575 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
576 * is not already bound (can be expensive)
577 * @param stack the stack to initialize the stack layout with
579 private void updateHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) {
580 RecentsConfiguration config = Recents.getConfiguration();
581 SystemServicesProxy ssp = Recents.getSystemServices();
582 Rect systemInsets = new Rect();
583 ssp.getStableInsets(systemInsets);
584 Rect windowRect = ssp.getWindowRect();
585 calculateWindowStableInsets(systemInsets, windowRect);
586 windowRect.offsetTo(0, 0);
588 // Update the configuration for the current state
589 config.update(systemInsets);
591 if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
592 // Try and pre-emptively bind the search widget on startup to ensure that we
593 // have the right thumbnail bounds to animate to.
594 // Note: We have to reload the widget id before we get the task stack bounds below
595 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
596 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
599 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
600 mSearchBarBounds, mTaskStackBounds);
602 // Rebind the header bar and draw it for the transition
603 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
604 Rect taskStackBounds = new Rect(mTaskStackBounds);
605 stackLayout.setSystemInsets(systemInsets);
607 stackLayout.initialize(taskStackBounds,
608 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
609 mDummyStackView.setTasks(stack, false /* notifyStackChanges */,
610 false /* relayoutTaskStack */);
612 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
613 if (!taskViewBounds.equals(mLastTaskViewBounds)) {
614 mLastTaskViewBounds.set(taskViewBounds);
616 int taskViewWidth = taskViewBounds.width();
617 synchronized (mHeaderBarLock) {
619 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
620 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
621 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
627 * Given the stable insets and the rect for our window, calculates the insets that affect our
630 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
631 Rect displayRect = Recents.getSystemServices().getDisplayRect();
633 // Display rect without insets - available app space
634 Rect appRect = new Rect(displayRect);
635 appRect.inset(inOutInsets);
637 // Our window intersected with available app space
638 Rect windowRectWithInsets = new Rect(windowRect);
639 windowRectWithInsets.intersect(appRect);
640 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
641 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
642 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
643 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
647 * Preloads the icon of a task.
649 private void preloadIcon(ActivityManager.RunningTaskInfo task) {
650 // Ensure that we load the running task's icon
651 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
652 launchOpts.runningTaskId = task.id;
653 launchOpts.loadThumbnails = false;
654 launchOpts.onlyLoadForCache = true;
655 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
659 * Caches the header thumbnail used for a window animation asynchronously into
660 * {@link #mThumbnailTransitionBitmapCache}.
662 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
663 TaskStack stack, TaskStackView stackView) {
664 preloadIcon(topTask);
666 // Update the header bar if necessary
667 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
669 // Update the destination rect
670 final Task toTask = new Task();
671 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
672 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
675 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
676 mHandler.post(new Runnable() {
679 mThumbnailTransitionBitmapCache = transitionBitmap;
680 mThumbnailTransitionBitmapCacheKey = toTask;
688 * Creates the activity options for a unknown state->recents transition.
690 protected ActivityOptions getUnknownTransitionActivityOptions() {
691 return ActivityOptions.makeCustomAnimation(mContext,
692 R.anim.recents_from_unknown_enter,
693 R.anim.recents_from_unknown_exit,
698 * Creates the activity options for a home->recents transition.
700 protected ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
701 if (fromSearchHome) {
702 return ActivityOptions.makeCustomAnimation(mContext,
703 R.anim.recents_from_search_launcher_enter,
704 R.anim.recents_from_search_launcher_exit,
707 return ActivityOptions.makeCustomAnimation(mContext,
708 R.anim.recents_from_launcher_enter,
709 R.anim.recents_from_launcher_exit,
714 * Creates the activity options for an app->recents transition.
716 private ActivityOptions getThumbnailTransitionActivityOptions(
717 ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
718 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
719 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
720 ArrayList<Task> tasks = stackView.getStack().getStackTasks();
721 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
722 TaskStackViewScroller stackScroller = stackView.getScroller();
724 stackView.updateLayoutAlgorithm(true /* boundScroll */);
725 stackView.updateToInitialState();
727 for (int i = tasks.size() - 1; i >= 0; i--) {
728 Task task = tasks.get(i);
729 if (task.isFreeformTask()) {
730 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
731 stackScroller.getStackScroll(), mTmpTransform, null);
732 Rect toTaskRect = new Rect();
733 mTmpTransform.rect.round(toTaskRect);
734 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
735 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
738 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
739 specs.toArray(specsArray);
740 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
741 specsArray, mHandler, null, this);
743 // Update the destination rect
744 Task toTask = new Task();
745 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
746 RectF toTaskRect = toTransform.rect;
747 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
748 if (thumbnail != null) {
749 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
750 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
751 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
753 // If both the screenshot and thumbnail fails, then just fall back to the default transition
754 return getUnknownTransitionActivityOptions();
758 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
759 TaskViewTransform toTransform) {
761 if (mThumbnailTransitionBitmapCacheKey != null
762 && mThumbnailTransitionBitmapCacheKey.key != null
763 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
764 thumbnail = mThumbnailTransitionBitmapCache;
765 mThumbnailTransitionBitmapCacheKey = null;
766 mThumbnailTransitionBitmapCache = null;
768 preloadIcon(topTask);
769 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
775 * Returns the transition rect for the given task id.
777 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
778 Task runningTaskOut) {
779 // Find the running task in the TaskStack
780 TaskStack stack = stackView.getStack();
781 Task launchTask = stack.getLaunchTarget();
782 if (launchTask != null) {
783 runningTaskOut.copyFrom(launchTask);
785 // If no task is specified or we can not find the task just use the front most one
786 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
787 runningTaskOut.copyFrom(launchTask);
790 // Get the transform for the running task
791 stackView.updateLayoutAlgorithm(true /* boundScroll */);
792 stackView.updateToInitialState();
793 mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
794 stackView.getScroller().getStackScroll(), mTmpTransform, null);
795 return mTmpTransform;
799 * Draws the header of a task used for the window animation into a bitmap.
801 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
802 SystemServicesProxy ssp = Recents.getSystemServices();
803 if (toTransform != null && toTask.key != null) {
805 synchronized (mHeaderBarLock) {
806 int toHeaderWidth = (int) toTransform.rect.width();
807 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
808 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
809 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
810 (int) toTransform.rect.height());
811 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
812 Bitmap.Config.ARGB_8888);
813 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
814 thumbnail.eraseColor(0xFFff0000);
816 Canvas c = new Canvas(thumbnail);
817 c.scale(toTransform.scale, toTransform.scale);
818 mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
820 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
825 return thumbnail.createAshmemBitmap();
831 * Shows the recents activity
833 protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
834 boolean isTopTaskHome, boolean animate) {
835 RecentsTaskLoader loader = Recents.getTaskLoader();
837 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
838 // should always preload the tasks now. If we are dragging in recents, reload them as
839 // the stacks might have changed.
840 if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
841 // Create a new load plan if preloadRecents() was never triggered
842 sInstanceLoadPlan = loader.createLoadPlan(mContext);
844 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
845 loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
847 TaskStack stack = sInstanceLoadPlan.getTaskStack();
849 // Update the header bar if necessary
850 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
852 // Prepare the dummy stack for the transition
853 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
854 mDummyStackView.computeStackVisibilityReport();
857 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
858 startRecentsActivity(topTask, opts, false /* fromHome */,
859 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
863 boolean hasRecentTasks = stack.getTaskCount() > 0;
864 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
866 if (useThumbnailTransition) {
867 // Try starting with a thumbnail transition
868 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
870 startRecentsActivity(topTask, opts, false /* fromHome */,
871 false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
873 // Fall through below to the non-thumbnail transition
874 useThumbnailTransition = false;
878 if (!useThumbnailTransition) {
879 // If there is no thumbnail transition, but is launching from home into recents, then
880 // use a quick home transition and do the animation from home
881 if (hasRecentTasks) {
882 SystemServicesProxy ssp = Recents.getSystemServices();
883 String homeActivityPackage = ssp.getHomeActivityPackageName();
884 String searchWidgetPackage = null;
885 if (RecentsDebugFlags.Static.EnableSearchBar) {
886 searchWidgetPackage = Prefs.getString(mContext,
887 Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
889 AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
890 if (searchWidgetInfo != null) {
891 searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
895 // Determine whether we are coming from a search owned home activity
896 boolean fromSearchHome = (homeActivityPackage != null) &&
897 homeActivityPackage.equals(searchWidgetPackage);
898 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
899 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
900 false /* fromThumbnail */, stackVr);
902 // Otherwise we do the normal fade from an unknown source
903 ActivityOptions opts = getUnknownTransitionActivityOptions();
904 startRecentsActivity(topTask, opts, true /* fromHome */,
905 false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
908 mLastToggleTime = SystemClock.elapsedRealtime();
912 * Starts the recents activity.
914 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
915 ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
916 boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) {
917 // Update the configuration based on the launch options
918 RecentsConfiguration config = Recents.getConfiguration();
919 RecentsActivityLaunchState launchState = config.getLaunchState();
920 launchState.launchedFromHome = fromSearchHome || fromHome;
921 launchState.launchedFromSearchHome = fromSearchHome;
922 launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
923 launchState.launchedFromAppDocked = mLaunchedWhileDocking;
924 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
925 launchState.launchedWithAltTab = mTriggeredFromAltTab;
926 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
927 launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
928 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
929 launchState.launchedHasConfigurationChanged = false;
930 launchState.launchedViaDragGesture = mDraggingInRecents;
931 launchState.launchedWhileDocking = mLaunchedWhileDocking;
933 Intent intent = new Intent();
934 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
935 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
936 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
937 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
940 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
942 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
944 mCanReuseTaskStackViews = true;
945 EventBus.getDefault().send(new RecentsActivityStartingEvent());
948 /**** OnAnimationFinishedListener Implementation ****/
951 public void onAnimationFinished() {
952 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());