blob: 4dae7460f30abdfc07385af7d29d60127d406c56 [file] [log] [blame]
Winson190fe3bf2015-10-20 14:57:24 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents;
18
Winsone693aaf2016-03-01 12:05:59 -080019import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
20
Winson190fe3bf2015-10-20 14:57:24 -070021import android.app.ActivityManager;
22import android.app.ActivityOptions;
Winson190fe3bf2015-10-20 14:57:24 -070023import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070024import android.content.Context;
25import android.content.Intent;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
29import android.graphics.Rect;
Winson3150e572015-10-23 15:07:24 -070030import android.graphics.RectF;
Winson22574af2016-03-23 19:00:28 -070031import android.graphics.drawable.Drawable;
Winson190fe3bf2015-10-20 14:57:24 -070032import android.os.Handler;
33import android.os.SystemClock;
34import android.os.UserHandle;
Winson1b585612015-11-06 09:16:26 -080035import android.util.Log;
Winson190fe3bf2015-10-20 14:57:24 -070036import android.util.MutableBoolean;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -070037import android.view.AppTransitionAnimationSpec;
Winson190fe3bf2015-10-20 14:57:24 -070038import android.view.LayoutInflater;
39import android.view.View;
Winsonb61e6542016-02-04 14:37:18 -080040import android.view.ViewConfiguration;
Winsonc0d70582016-01-29 10:24:39 -080041
Winson190fe3bf2015-10-20 14:57:24 -070042import com.android.internal.logging.MetricsLogger;
Winson190fe3bf2015-10-20 14:57:24 -070043import com.android.systemui.R;
44import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070045import com.android.systemui.recents.events.EventBus;
Jorim Jaggi899327f2016-02-25 20:44:18 -050046import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
Winson1b585612015-11-06 09:16:26 -080047import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
Jorim Jaggi2adba072016-03-03 13:43:39 +010048import com.android.systemui.recents.events.activity.ForcedResizableEvent;
Winson412e1802015-10-20 16:57:57 -070049import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070050import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Winsonb61e6542016-02-04 14:37:18 -080051import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080052import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
Winson412e1802015-10-20 16:57:57 -070053import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
Winson190fe3bf2015-10-20 14:57:24 -070054import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
55import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
Jorim Jaggidd98d412015-11-18 15:57:38 -080056import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
57import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
Winson6b92c6e2015-11-06 13:11:16 -080058import com.android.systemui.recents.misc.DozeTrigger;
Winsonab84fc52015-10-23 11:52:07 -070059import com.android.systemui.recents.misc.ForegroundThread;
Winson190fe3bf2015-10-20 14:57:24 -070060import com.android.systemui.recents.misc.SystemServicesProxy;
Jaewan Kim938a50b2016-03-14 17:35:43 +090061import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
Winson190fe3bf2015-10-20 14:57:24 -070062import com.android.systemui.recents.model.RecentsTaskLoadPlan;
63import com.android.systemui.recents.model.RecentsTaskLoader;
64import com.android.systemui.recents.model.Task;
65import com.android.systemui.recents.model.TaskGrouping;
66import com.android.systemui.recents.model.TaskStack;
Winson36a5a2c2015-10-29 18:04:39 -070067import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
Winson1b585612015-11-06 09:16:26 -080068import com.android.systemui.recents.views.TaskStackView;
Winsone693aaf2016-03-01 12:05:59 -080069import com.android.systemui.recents.views.TaskStackViewScroller;
Winson190fe3bf2015-10-20 14:57:24 -070070import com.android.systemui.recents.views.TaskViewHeader;
71import com.android.systemui.recents.views.TaskViewTransform;
Winsond8b1d632016-01-04 17:51:18 -080072import com.android.systemui.statusbar.BaseStatusBar;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080073import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
Winson190fe3bf2015-10-20 14:57:24 -070074import com.android.systemui.statusbar.phone.PhoneStatusBar;
75
76import java.util.ArrayList;
77
78/**
79 * An implementation of the Recents component for the current user. For secondary users, this can
80 * be called remotely from the system user.
81 */
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080082public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
Winson190fe3bf2015-10-20 14:57:24 -070083
84 private final static String TAG = "RecentsImpl";
Winsonb61e6542016-02-04 14:37:18 -080085
Winson6b92c6e2015-11-06 13:11:16 -080086 // The minimum amount of time between each recents button press that we will handle
87 private final static int MIN_TOGGLE_DELAY_MS = 350;
Winsonb61e6542016-02-04 14:37:18 -080088
Winson6b92c6e2015-11-06 13:11:16 -080089 // 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;
Winson190fe3bf2015-10-20 14:57:24 -070093
94 public final static String RECENTS_PACKAGE = "com.android.systemui";
95 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
Winsone693aaf2016-03-01 12:05:59 -080096
Winson190fe3bf2015-10-20 14:57:24 -070097 /**
Jaewan Kim938a50b2016-03-14 17:35:43 +090098 * An implementation of TaskStackListener, that allows us to listen for changes to the system
Winson190fe3bf2015-10-20 14:57:24 -070099 * task stacks and update recents accordingly.
100 */
Jaewan Kim938a50b2016-03-14 17:35:43 +0900101 class TaskStackListenerImpl extends TaskStackListener {
Winson190fe3bf2015-10-20 14:57:24 -0700102 @Override
103 public void onTaskStackChanged() {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900104 // Preloads the next task
Jorim Jaggia0fdeec2016-01-07 14:42:28 +0100105 RecentsConfiguration config = Recents.getConfiguration();
Winson190fe3bf2015-10-20 14:57:24 -0700106 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winsone7f138c2015-10-22 16:15:21 -0700107 RecentsTaskLoader loader = Recents.getTaskLoader();
108 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700109 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
110
111 // Load the next task only if we aren't svelte
112 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800113 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700114 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
117 // previous one.
118 if (runningTaskInfo != null) {
119 launchOpts.runningTaskId = runningTaskInfo.id;
120 }
121 launchOpts.numVisibleTasks = 2;
122 launchOpts.numVisibleTaskThumbnails = 2;
123 launchOpts.onlyLoadForCache = true;
124 launchOpts.onlyLoadPausedActivities = true;
125 loader.loadTasks(mContext, plan, launchOpts);
126 }
Winson190fe3bf2015-10-20 14:57:24 -0700127 }
Jorim Jaggi2adba072016-03-03 13:43:39 +0100128
129 @Override
130 public void onActivityForcedResizable(String packageName, int taskId) {
131 EventBus.getDefault().sendOntoMainThread(
132 new ForcedResizableEvent(packageName, taskId));
133
134 }
Winson190fe3bf2015-10-20 14:57:24 -0700135 }
136
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700137 protected static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700138
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700139 protected Context mContext;
140 protected Handler mHandler;
Winson190fe3bf2015-10-20 14:57:24 -0700141 TaskStackListenerImpl mTaskStackListener;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800142 boolean mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800143 boolean mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700144
145 // Task launching
Winson190fe3bf2015-10-20 14:57:24 -0700146 Rect mTaskStackBounds = new Rect();
147 Rect mLastTaskViewBounds = new Rect();
148 TaskViewTransform mTmpTransform = new TaskViewTransform();
149 int mStatusBarHeight;
150 int mNavBarHeight;
151 int mNavBarWidth;
152 int mTaskBarHeight;
153
154 // Header (for transition)
155 TaskViewHeader mHeaderBar;
156 final Object mHeaderBarLock = new Object();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700157 protected TaskStackView mDummyStackView;
Winson190fe3bf2015-10-20 14:57:24 -0700158
159 // Variables to keep track of if we need to start recents after binding
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700160 protected boolean mTriggeredFromAltTab;
161 protected long mLastToggleTime;
Winson6b92c6e2015-11-06 13:11:16 -0800162 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
163 @Override
164 public void run() {
165 // When this fires, then the user has not released alt-tab for at least
166 // FAST_ALT_TAB_DELAY_MS milliseconds
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800167 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
168 false /* reloadTasks */);
Winson6b92c6e2015-11-06 13:11:16 -0800169 }
170 });
Winson190fe3bf2015-10-20 14:57:24 -0700171
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700172 protected Bitmap mThumbnailTransitionBitmapCache;
Winson190fe3bf2015-10-20 14:57:24 -0700173 Task mThumbnailTransitionBitmapCacheKey;
174
Winson190fe3bf2015-10-20 14:57:24 -0700175 public RecentsImpl(Context context) {
176 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700177 mHandler = new Handler();
Winson190fe3bf2015-10-20 14:57:24 -0700178
Winsonab84fc52015-10-23 11:52:07 -0700179 // Initialize the static foreground thread
180 ForegroundThread.get();
181
Winson190fe3bf2015-10-20 14:57:24 -0700182 // Register the task stack listener
Jaewan Kim938a50b2016-03-14 17:35:43 +0900183 mTaskStackListener = new TaskStackListenerImpl();
Winsone7f138c2015-10-22 16:15:21 -0700184 SystemServicesProxy ssp = Recents.getSystemServices();
185 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700186
187 // Initialize the static configuration resources
Winsonb94443d2016-01-07 15:34:13 -0800188 reloadHeaderBarLayout();
Winson008ee15f2016-03-18 17:17:25 -0700189 updateHeaderBarLayout(null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700190
191 // When we start, preload the data associated with the previous recent tasks.
192 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700193 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700194 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800195 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700196 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winson Chung296278a2015-12-17 12:09:02 -0500197 launchOpts.numVisibleTasks = loader.getIconCacheSize();
Winson190fe3bf2015-10-20 14:57:24 -0700198 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
199 launchOpts.onlyLoadForCache = true;
200 loader.loadTasks(mContext, plan, launchOpts);
201 }
202
203 public void onBootCompleted() {
Winson008ee15f2016-03-18 17:17:25 -0700204 updateHeaderBarLayout(null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700205 }
206
Winson190fe3bf2015-10-20 14:57:24 -0700207 public void onConfigurationChanged() {
Winsonb94443d2016-01-07 15:34:13 -0800208 reloadHeaderBarLayout();
Winson008ee15f2016-03-18 17:17:25 -0700209 updateHeaderBarLayout(null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700210 }
211
212 /**
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)}.
216 */
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);
222 }
223 }
224
225 /**
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)}.
229 */
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);
235 }
236 }
237
Jorim Jaggibb42a462015-11-20 16:27:16 -0800238 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
Jorim Jaggie161f082016-02-05 14:26:16 -0800239 boolean animate, boolean launchedWhileDockingTask) {
Winson190fe3bf2015-10-20 14:57:24 -0700240 mTriggeredFromAltTab = triggeredFromAltTab;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800241 mDraggingInRecents = draggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800242 mLaunchedWhileDocking = launchedWhileDockingTask;
Winsone693aaf2016-03-01 12:05:59 -0800243 if (mFastAltTabTrigger.isAsleep()) {
244 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
245 mFastAltTabTrigger.stopDozing();
Winson6b92c6e2015-11-06 13:11:16 -0800246 } else if (mFastAltTabTrigger.isDozing()) {
Winsone693aaf2016-03-01 12:05:59 -0800247 // 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.
Winson6b92c6e2015-11-06 13:11:16 -0800252 if (!triggeredFromAltTab) {
253 return;
254 }
255 mFastAltTabTrigger.stopDozing();
Winsone693aaf2016-03-01 12:05:59 -0800256 } 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();
260 return;
Winson6b92c6e2015-11-06 13:11:16 -0800261 }
Winson190fe3bf2015-10-20 14:57:24 -0700262
263 try {
264 // Check if the top task is in the home stack, and start the recents activity
Winsone7f138c2015-10-22 16:15:21 -0700265 SystemServicesProxy ssp = Recents.getSystemServices();
266 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700267 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700268 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Jorim Jaggibb42a462015-11-20 16:27:16 -0800269 startRecentsActivity(topTask, isTopTaskHome.value, animate);
Winson190fe3bf2015-10-20 14:57:24 -0700270 }
271 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800272 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700273 }
274 }
275
Winson190fe3bf2015-10-20 14:57:24 -0700276 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
Winson2799eca2016-02-25 12:10:42 -0800277 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
278 // The user has released alt-tab before the trigger has run, so just show the next
279 // task immediately
280 showNextTask();
Winson6b92c6e2015-11-06 13:11:16 -0800281
Winson2799eca2016-02-25 12:10:42 -0800282 // Cancel the fast alt-tab trigger
283 mFastAltTabTrigger.stopDozing();
Winson2799eca2016-02-25 12:10:42 -0800284 return;
Winson190fe3bf2015-10-20 14:57:24 -0700285 }
Winson2799eca2016-02-25 12:10:42 -0800286
287 // Defer to the activity to handle hiding recents, if it handles it, then it must still
288 // be visible
289 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
290 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700291 }
292
Winson190fe3bf2015-10-20 14:57:24 -0700293 public void toggleRecents() {
Winson6b92c6e2015-11-06 13:11:16 -0800294 // Skip this toggle if we are already waiting to trigger recents via alt-tab
295 if (mFastAltTabTrigger.isDozing()) {
296 return;
297 }
298
Jorim Jaggidd98d412015-11-18 15:57:38 -0800299 mDraggingInRecents = false;
Jorim Jaggie161f082016-02-05 14:26:16 -0800300 mLaunchedWhileDocking = false;
Winson190fe3bf2015-10-20 14:57:24 -0700301 mTriggeredFromAltTab = false;
302
303 try {
Winsone7f138c2015-10-22 16:15:21 -0700304 SystemServicesProxy ssp = Recents.getSystemServices();
305 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700306 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsonb61e6542016-02-04 14:37:18 -0800307 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
308
Winsone7f138c2015-10-22 16:15:21 -0700309 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Winsone693aaf2016-03-01 12:05:59 -0800310 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
Winson5da43472015-11-04 17:39:55 -0800311 RecentsConfiguration config = Recents.getConfiguration();
312 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson Chungead5c0f2015-12-14 11:18:57 -0500313 if (!launchState.launchedWithAltTab) {
Winsonb61e6542016-02-04 14:37:18 -0800314 // If the user taps quickly
Winsone693aaf2016-03-01 12:05:59 -0800315 if (!debugFlags.isPagingEnabled() ||
316 (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
317 elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
Winsonb61e6542016-02-04 14:37:18 -0800318 // Launch the next focused task
319 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
320 } else {
321 // Notify recents to move onto the next task
322 EventBus.getDefault().post(new IterateRecentsEvent());
323 }
Winson0d14d4d2015-10-26 17:05:04 -0700324 } else {
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
328 // account
Winsonb61e6542016-02-04 14:37:18 -0800329 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700330 return;
331 }
332
333 EventBus.getDefault().post(new ToggleRecentsEvent());
334 mLastToggleTime = SystemClock.elapsedRealtime();
335 }
Winson190fe3bf2015-10-20 14:57:24 -0700336 return;
337 } else {
Winson0d14d4d2015-10-26 17:05:04 -0700338 // 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
341 // account
Winsonb61e6542016-02-04 14:37:18 -0800342 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700343 return;
344 }
345
Winson190fe3bf2015-10-20 14:57:24 -0700346 // Otherwise, start the recents activity
Jorim Jaggibb42a462015-11-20 16:27:16 -0800347 startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
Winsond8b1d632016-01-04 17:51:18 -0800348
349 // Only close the other system windows if we are actually showing recents
350 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
Winson0d14d4d2015-10-26 17:05:04 -0700351 mLastToggleTime = SystemClock.elapsedRealtime();
Winson190fe3bf2015-10-20 14:57:24 -0700352 }
353 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800354 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700355 }
356 }
357
Winson190fe3bf2015-10-20 14:57:24 -0700358 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.
Winsone7f138c2015-10-22 16:15:21 -0700361 SystemServicesProxy ssp = Recents.getSystemServices();
362 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700363 MutableBoolean topTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700364 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
Winson5b4e0d22016-02-16 18:11:35 -0800365 RecentsTaskLoader loader = Recents.getTaskLoader();
366 sInstanceLoadPlan = loader.createLoadPlan(mContext);
Winson190fe3bf2015-10-20 14:57:24 -0700367 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
Winson65c851e2016-01-20 12:43:35 -0800368 loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
Winson190fe3bf2015-10-20 14:57:24 -0700369 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winson4b057c62016-01-12 15:01:52 -0800370 if (stack.getTaskCount() > 0) {
Winsonab84fc52015-10-23 11:52:07 -0700371 // We try and draw the thumbnail transition bitmap in parallel before
372 // toggle/show recents is called
Winson190fe3bf2015-10-20 14:57:24 -0700373 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
374 }
375 }
376 }
377
Winson190fe3bf2015-10-20 14:57:24 -0700378 public void cancelPreloadingRecents() {
379 // Do nothing
380 }
381
Jorim Jaggidd98d412015-11-18 15:57:38 -0800382 public void onDraggingInRecents(float distanceFromTop) {
383 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
384 }
385
Jorim Jaggidd98d412015-11-18 15:57:38 -0800386 public void onDraggingInRecentsEnded(float velocity) {
387 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
388 }
389
Winson6b92c6e2015-11-06 13:11:16 -0800390 /**
391 * Transitions to the next recent task in the stack.
392 */
393 public void showNextTask() {
Winsone7f138c2015-10-22 16:15:21 -0700394 SystemServicesProxy ssp = Recents.getSystemServices();
Winson6b92c6e2015-11-06 13:11:16 -0800395 RecentsTaskLoader loader = Recents.getTaskLoader();
396 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800397 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson6b92c6e2015-11-06 13:11:16 -0800398 TaskStack focusedStack = plan.getTaskStack();
399
400 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800401 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson6b92c6e2015-11-06 13:11:16 -0800402
403 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
404 // Return early if there is no running task
405 if (runningTask == null) return;
Winson6b92c6e2015-11-06 13:11:16 -0800406
407 // Find the task in the recents list
Winsone86deb82015-11-12 09:32:10 -0800408 boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
Winson250608a2015-11-24 15:00:31 -0800409 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson6b92c6e2015-11-06 13:11:16 -0800410 Task toTask = null;
411 ActivityOptions launchOpts = null;
412 int taskCount = tasks.size();
413 for (int i = taskCount - 1; i >= 1; i--) {
414 Task task = tasks.get(i);
Winsone86deb82015-11-12 09:32:10 -0800415 if (isTopTaskHome) {
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);
420 break;
421 } else if (task.key.id == runningTask.id) {
Winson6b92c6e2015-11-06 13:11:16 -0800422 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);
426 break;
427 }
428 }
429
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));
435 return;
436 }
437
438 // Launch the task
Wale Ogunwale64ae08a2016-03-28 08:15:27 -0700439 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
Winson6b92c6e2015-11-06 13:11:16 -0800440 }
441
442 /**
443 * Transitions to the next affiliated task.
444 */
445 public void showRelativeAffiliatedTask(boolean showNextTask) {
446 SystemServicesProxy ssp = Recents.getSystemServices();
Winsone7f138c2015-10-22 16:15:21 -0700447 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700448 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800449 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700450 TaskStack focusedStack = plan.getTaskStack();
451
452 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800453 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson190fe3bf2015-10-20 14:57:24 -0700454
Winsone7f138c2015-10-22 16:15:21 -0700455 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700456 // 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)
Winson5510f6c2015-10-27 12:11:26 -0700459 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
Winson190fe3bf2015-10-20 14:57:24 -0700460
461 // Find the task in the recents list
Winson250608a2015-11-24 15:00:31 -0800462 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson190fe3bf2015-10-20 14:57:24 -0700463 Task toTask = null;
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;
472 if (showNextTask) {
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);
477 } else {
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);
482 }
483 if (toTaskKey != null) {
484 toTask = focusedStack.findTaskWithId(toTaskKey.id);
485 }
486 numAffiliatedTasks = group.getTaskCount();
487 break;
488 }
489 }
490
491 // Return early if there is no next task
492 if (toTask == null) {
493 if (numAffiliatedTasks > 1) {
494 if (showNextTask) {
Winsone7f138c2015-10-22 16:15:21 -0700495 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700496 ActivityOptions.makeCustomInPlaceAnimation(mContext,
497 R.anim.recents_launch_next_affiliated_task_bounce));
498 } else {
Winsone7f138c2015-10-22 16:15:21 -0700499 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700500 ActivityOptions.makeCustomInPlaceAnimation(mContext,
501 R.anim.recents_launch_prev_affiliated_task_bounce));
502 }
503 }
504 return;
505 }
506
507 // Keep track of actually launched affiliated tasks
508 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
509
510 // Launch the task
Wale Ogunwale64ae08a2016-03-28 08:15:27 -0700511 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700512 }
513
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);
518 }
519
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);
524 }
525
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800526 public void dockTopTask(int topTaskId, int dragMode,
527 int stackCreateMode, Rect initialBounds) {
Jorim Jaggi75b25972015-10-21 14:51:10 +0200528 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggi9511b0f2016-01-29 19:12:44 -0800529
530 // Make sure we inform DividerView before we actually start the activity so we can change
531 // the resize mode already.
Chong Zhange4fbd322016-03-01 14:44:03 -0800532 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
Jorim Jaggi899327f2016-02-25 20:44:18 -0500533 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
Chong Zhange4fbd322016-03-01 14:44:03 -0800534 showRecents(
535 false /* triggeredFromAltTab */,
536 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
537 false /* animate */,
Winsone693aaf2016-03-01 12:05:59 -0800538 true /* launchedWhileDockingTask*/);
Chong Zhange4fbd322016-03-01 14:44:03 -0800539 }
Jorim Jaggi75b25972015-10-21 14:51:10 +0200540 }
541
Winson190fe3bf2015-10-20 14:57:24 -0700542 /**
543 * Returns the preloaded load plan and invalidates it.
544 */
545 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
546 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
547 sInstanceLoadPlan = null;
548 return plan;
549 }
550
551 /**
Winsonb94443d2016-01-07 15:34:13 -0800552 * Reloads all the layouts for the header bar transition.
553 */
554 private void reloadHeaderBarLayout() {
555 Resources res = mContext.getResources();
556 LayoutInflater inflater = LayoutInflater.from(mContext);
557
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);
Winson21700932016-03-24 17:26:23 -0700564 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(res,
565 R.dimen.recents_task_view_header_height,
566 R.dimen.recents_task_view_header_height,
567 R.dimen.recents_task_view_header_height,
568 R.dimen.recents_task_view_header_height_tablet_land,
569 R.dimen.recents_task_view_header_height,
570 R.dimen.recents_task_view_header_height_tablet_land);
Winson88737542016-02-17 13:27:33 -0800571 mDummyStackView = new TaskStackView(mContext);
Winsonb94443d2016-01-07 15:34:13 -0800572 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
573 null, false);
574 }
575
576 /**
Winson190fe3bf2015-10-20 14:57:24 -0700577 * Prepares the header bar layout for the next transition, if the task view bounds has changed
578 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
579 *
Winsonf0d1c442015-12-01 11:04:45 -0800580 * @param stack the stack to initialize the stack layout with
Winson190fe3bf2015-10-20 14:57:24 -0700581 */
Winson008ee15f2016-03-18 17:17:25 -0700582 private void updateHeaderBarLayout(TaskStack stack) {
Winsone7f138c2015-10-22 16:15:21 -0700583 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800584 Rect systemInsets = new Rect();
585 ssp.getStableInsets(systemInsets);
Winsone7f138c2015-10-22 16:15:21 -0700586 Rect windowRect = ssp.getWindowRect();
Winsoncf9b8322016-03-31 15:36:07 -0700587 // When docked, the nav bar insets are consumed and the activity is measured without insets.
588 // However, the window bounds include the insets, so we need to subtract them here to make
589 // them identical.
590 if (ssp.hasDockedTask()) {
591 windowRect.bottom -= systemInsets.bottom;
592 systemInsets.bottom = 0;
593 }
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800594 calculateWindowStableInsets(systemInsets, windowRect);
595 windowRect.offsetTo(0, 0);
Winson190fe3bf2015-10-20 14:57:24 -0700596
Winson59924fe2016-03-17 14:13:18 -0700597 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
Winson190fe3bf2015-10-20 14:57:24 -0700598
599 // Rebind the header bar and draw it for the transition
Winson88737542016-02-17 13:27:33 -0800600 stackLayout.setSystemInsets(systemInsets);
Winsonf0d1c442015-12-01 11:04:45 -0800601 if (stack != null) {
Winsoncf9b8322016-03-31 15:36:07 -0700602 stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
603 mTaskStackBounds);
604 stackLayout.initialize(windowRect, mTaskStackBounds,
Winsonf0d1c442015-12-01 11:04:45 -0800605 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
Winsona1ededd2016-03-25 12:23:12 -0700606 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
Winsonf0d1c442015-12-01 11:04:45 -0800607 }
Winson88737542016-02-17 13:27:33 -0800608 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
Winson190fe3bf2015-10-20 14:57:24 -0700609 if (!taskViewBounds.equals(mLastTaskViewBounds)) {
610 mLastTaskViewBounds.set(taskViewBounds);
611
612 int taskViewWidth = taskViewBounds.width();
613 synchronized (mHeaderBarLock) {
614 mHeaderBar.measure(
615 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
616 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
617 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
618 }
619 }
620 }
621
622 /**
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800623 * Given the stable insets and the rect for our window, calculates the insets that affect our
624 * window.
625 */
626 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
627 Rect displayRect = Recents.getSystemServices().getDisplayRect();
628
629 // Display rect without insets - available app space
630 Rect appRect = new Rect(displayRect);
631 appRect.inset(inOutInsets);
632
633 // Our window intersected with available app space
634 Rect windowRectWithInsets = new Rect(windowRect);
635 windowRectWithInsets.intersect(appRect);
636 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
637 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
638 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
639 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
640 }
641
642 /**
Winson190fe3bf2015-10-20 14:57:24 -0700643 * Preloads the icon of a task.
644 */
645 private void preloadIcon(ActivityManager.RunningTaskInfo task) {
Winson190fe3bf2015-10-20 14:57:24 -0700646 // Ensure that we load the running task's icon
647 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
648 launchOpts.runningTaskId = task.id;
649 launchOpts.loadThumbnails = false;
650 launchOpts.onlyLoadForCache = true;
Winsone7f138c2015-10-22 16:15:21 -0700651 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700652 }
653
654 /**
655 * Caches the header thumbnail used for a window animation asynchronously into
656 * {@link #mThumbnailTransitionBitmapCache}.
657 */
658 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
659 TaskStack stack, TaskStackView stackView) {
660 preloadIcon(topTask);
661
662 // Update the header bar if necessary
Winson008ee15f2016-03-18 17:17:25 -0700663 updateHeaderBarLayout(stack);
Winson190fe3bf2015-10-20 14:57:24 -0700664
665 // Update the destination rect
Winson190fe3bf2015-10-20 14:57:24 -0700666 final Task toTask = new Task();
Winsone693aaf2016-03-01 12:05:59 -0800667 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
Winson3fb67562015-11-11 10:39:03 -0800668 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
Winson190fe3bf2015-10-20 14:57:24 -0700669 @Override
Winsonab84fc52015-10-23 11:52:07 -0700670 public void run() {
671 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
Winson7845e8c2016-03-29 10:23:19 -0700672 if (transitionBitmap != null) {
673 mHandler.post(new Runnable() {
674 @Override
675 public void run() {
676 mThumbnailTransitionBitmapCache = transitionBitmap;
677 mThumbnailTransitionBitmapCacheKey = toTask;
678 }
679 });
680 } else {
681 Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " +
682 toTransform);
683 }
Winson190fe3bf2015-10-20 14:57:24 -0700684 }
Winsonab84fc52015-10-23 11:52:07 -0700685 });
Winson190fe3bf2015-10-20 14:57:24 -0700686 }
687
688 /**
689 * Creates the activity options for a unknown state->recents transition.
690 */
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700691 protected ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700692 return ActivityOptions.makeCustomAnimation(mContext,
693 R.anim.recents_from_unknown_enter,
694 R.anim.recents_from_unknown_exit,
Winson3fb67562015-11-11 10:39:03 -0800695 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700696 }
697
698 /**
699 * Creates the activity options for a home->recents transition.
700 */
Winson008ee15f2016-03-18 17:17:25 -0700701 protected ActivityOptions getHomeTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700702 return ActivityOptions.makeCustomAnimation(mContext,
703 R.anim.recents_from_launcher_enter,
704 R.anim.recents_from_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800705 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700706 }
707
708 /**
709 * Creates the activity options for an app->recents transition.
710 */
711 private ActivityOptions getThumbnailTransitionActivityOptions(
Winsone693aaf2016-03-01 12:05:59 -0800712 ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700713 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
714 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
Winsone693aaf2016-03-01 12:05:59 -0800715 ArrayList<Task> tasks = stackView.getStack().getStackTasks();
716 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
717 TaskStackViewScroller stackScroller = stackView.getScroller();
718
Winson003eda62016-03-11 14:56:00 -0800719 stackView.updateLayoutAlgorithm(true /* boundScroll */);
Winson619e40c2016-03-25 16:12:35 -0700720 stackView.updateToInitialState(true /* scrollToInitialState */);
Winsone693aaf2016-03-01 12:05:59 -0800721
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700722 for (int i = tasks.size() - 1; i >= 0; i--) {
723 Task task = tasks.get(i);
Winson387aac62015-11-25 11:18:56 -0800724 if (task.isFreeformTask()) {
Winsone693aaf2016-03-01 12:05:59 -0800725 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
726 stackScroller.getStackScroll(), mTmpTransform, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700727 Rect toTaskRect = new Rect();
728 mTmpTransform.rect.round(toTaskRect);
729 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
730 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
731 }
732 }
733 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
734 specs.toArray(specsArray);
735 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3fb67562015-11-11 10:39:03 -0800736 specsArray, mHandler, null, this);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700737 } else {
738 // Update the destination rect
739 Task toTask = new Task();
Winsone693aaf2016-03-01 12:05:59 -0800740 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700741 RectF toTaskRect = toTransform.rect;
742 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
743 if (thumbnail != null) {
744 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
745 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
Winson3fb67562015-11-11 10:39:03 -0800746 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700747 }
748 // If both the screenshot and thumbnail fails, then just fall back to the default transition
749 return getUnknownTransitionActivityOptions();
750 }
751 }
Winson190fe3bf2015-10-20 14:57:24 -0700752
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700753 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
754 TaskViewTransform toTransform) {
Winson190fe3bf2015-10-20 14:57:24 -0700755 Bitmap thumbnail;
756 if (mThumbnailTransitionBitmapCacheKey != null
757 && mThumbnailTransitionBitmapCacheKey.key != null
758 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
759 thumbnail = mThumbnailTransitionBitmapCache;
760 mThumbnailTransitionBitmapCacheKey = null;
761 mThumbnailTransitionBitmapCache = null;
762 } else {
763 preloadIcon(topTask);
764 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
765 }
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700766 return thumbnail;
Winson190fe3bf2015-10-20 14:57:24 -0700767 }
768
769 /**
770 * Returns the transition rect for the given task id.
771 */
Winsone693aaf2016-03-01 12:05:59 -0800772 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
773 Task runningTaskOut) {
Winson190fe3bf2015-10-20 14:57:24 -0700774 // Find the running task in the TaskStack
Winsone693aaf2016-03-01 12:05:59 -0800775 TaskStack stack = stackView.getStack();
Winson65c851e2016-01-20 12:43:35 -0800776 Task launchTask = stack.getLaunchTarget();
777 if (launchTask != null) {
778 runningTaskOut.copyFrom(launchTask);
779 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700780 // If no task is specified or we can not find the task just use the front most one
Winson35a8b042016-01-22 09:41:09 -0800781 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
Winson65c851e2016-01-20 12:43:35 -0800782 runningTaskOut.copyFrom(launchTask);
Winson190fe3bf2015-10-20 14:57:24 -0700783 }
784
785 // Get the transform for the running task
Winson003eda62016-03-11 14:56:00 -0800786 stackView.updateLayoutAlgorithm(true /* boundScroll */);
Winson619e40c2016-03-25 16:12:35 -0700787 stackView.updateToInitialState(true /* scrollToInitialState */);
Winson7845e8c2016-03-29 10:23:19 -0700788 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
Winson190fe3bf2015-10-20 14:57:24 -0700789 stackView.getScroller().getStackScroll(), mTmpTransform, null);
790 return mTmpTransform;
791 }
792
793 /**
794 * Draws the header of a task used for the window animation into a bitmap.
795 */
796 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
Winson8be16342016-02-09 11:53:27 -0800797 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700798 if (toTransform != null && toTask.key != null) {
799 Bitmap thumbnail;
800 synchronized (mHeaderBarLock) {
Winson Chung509d0d02015-12-16 15:43:12 -0500801 int toHeaderWidth = (int) toTransform.rect.width();
Winson190fe3bf2015-10-20 14:57:24 -0700802 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
Winsondec44302016-04-01 11:25:35 -0700803 if (toHeaderWidth <= 0 || toHeaderHeight <= 0) {
804 return null;
805 }
Winson8be16342016-02-09 11:53:27 -0800806 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
Winson Chung509d0d02015-12-16 15:43:12 -0500807 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
808 (int) toTransform.rect.height());
Winson190fe3bf2015-10-20 14:57:24 -0700809 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
810 Bitmap.Config.ARGB_8888);
Winsonc742f972015-11-12 11:32:21 -0800811 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson190fe3bf2015-10-20 14:57:24 -0700812 thumbnail.eraseColor(0xFFff0000);
813 } else {
814 Canvas c = new Canvas(thumbnail);
815 c.scale(toTransform.scale, toTransform.scale);
Winson22574af2016-03-23 19:00:28 -0700816 // Workaround for b/27815919, reset the callback so that we do not trigger an
817 // invalidate on the header bar as a result of updating the icon
818 Drawable icon = mHeaderBar.getIconView().getDrawable();
819 if (icon != null) {
820 icon.setCallback(null);
821 }
Winson8be16342016-02-09 11:53:27 -0800822 mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
823 disabledInSafeMode);
Winsone693aaf2016-03-01 12:05:59 -0800824 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
Winson190fe3bf2015-10-20 14:57:24 -0700825 mHeaderBar.draw(c);
826 c.setBitmap(null);
827 }
828 }
829 return thumbnail.createAshmemBitmap();
830 }
831 return null;
832 }
833
834 /**
835 * Shows the recents activity
836 */
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700837 protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
Jorim Jaggibb42a462015-11-20 16:27:16 -0800838 boolean isTopTaskHome, boolean animate) {
Winsone7f138c2015-10-22 16:15:21 -0700839 RecentsTaskLoader loader = Recents.getTaskLoader();
Winsonc5b12dd2016-03-23 20:25:12 -0700840 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
Winson190fe3bf2015-10-20 14:57:24 -0700841
Winsone5f1faa2015-11-20 12:26:23 -0800842 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800843 // should always preload the tasks now. If we are dragging in recents, reload them as
844 // the stacks might have changed.
Jorim Jaggie161f082016-02-05 14:26:16 -0800845 if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
Winsone5f1faa2015-11-20 12:26:23 -0800846 // Create a new load plan if preloadRecents() was never triggered
Winson190fe3bf2015-10-20 14:57:24 -0700847 sInstanceLoadPlan = loader.createLoadPlan(mContext);
848 }
Jorim Jaggie161f082016-02-05 14:26:16 -0800849 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
Winson65c851e2016-01-20 12:43:35 -0800850 loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
Winson190fe3bf2015-10-20 14:57:24 -0700851 }
Winsonc5b12dd2016-03-23 20:25:12 -0700852
Winson190fe3bf2015-10-20 14:57:24 -0700853 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winsonc5b12dd2016-03-23 20:25:12 -0700854 boolean hasRecentTasks = stack.getTaskCount() > 0;
855 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
Winson190fe3bf2015-10-20 14:57:24 -0700856
Winsonf0d1c442015-12-01 11:04:45 -0800857 // Update the header bar if necessary
Winson008ee15f2016-03-18 17:17:25 -0700858 updateHeaderBarLayout(stack);
Winsonf0d1c442015-12-01 11:04:45 -0800859
Winson190fe3bf2015-10-20 14:57:24 -0700860 // Prepare the dummy stack for the transition
Winson36a5a2c2015-10-29 18:04:39 -0700861 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
Winson190fe3bf2015-10-20 14:57:24 -0700862 mDummyStackView.computeStackVisibilityReport();
Jorim Jaggibb42a462015-11-20 16:27:16 -0800863
Winsonc5b12dd2016-03-23 20:25:12 -0700864 // Update the launch state
865 launchState.launchedFromHome = false;
866 launchState.launchedFromApp = mLaunchedWhileDocking;
Winsonc69249f2016-03-28 13:38:39 -0700867 launchState.launchedViaDockGesture = mLaunchedWhileDocking;
Winsonc5b12dd2016-03-23 20:25:12 -0700868 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
Winsonc5b12dd2016-03-23 20:25:12 -0700869 launchState.launchedWithAltTab = mTriggeredFromAltTab;
Winsonc5b12dd2016-03-23 20:25:12 -0700870 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
871 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
Winsonc5b12dd2016-03-23 20:25:12 -0700872 launchState.launchedViaDragGesture = mDraggingInRecents;
Winsonc5b12dd2016-03-23 20:25:12 -0700873
Jorim Jaggibb42a462015-11-20 16:27:16 -0800874 if (!animate) {
Winsonc5b12dd2016-03-23 20:25:12 -0700875 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
Jorim Jaggibb42a462015-11-20 16:27:16 -0800876 return;
877 }
878
Winson190fe3bf2015-10-20 14:57:24 -0700879 if (useThumbnailTransition) {
Winsonc5b12dd2016-03-23 20:25:12 -0700880 launchState.launchedFromApp = true;
881
Winson190fe3bf2015-10-20 14:57:24 -0700882 // Try starting with a thumbnail transition
Winsone693aaf2016-03-01 12:05:59 -0800883 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
Winson190fe3bf2015-10-20 14:57:24 -0700884 if (opts != null) {
Winsonc5b12dd2016-03-23 20:25:12 -0700885 startRecentsActivity(opts);
Winson190fe3bf2015-10-20 14:57:24 -0700886 } else {
887 // Fall through below to the non-thumbnail transition
888 useThumbnailTransition = false;
889 }
890 }
891
892 if (!useThumbnailTransition) {
Winsonc5b12dd2016-03-23 20:25:12 -0700893 launchState.launchedFromHome = true;
894
Winson190fe3bf2015-10-20 14:57:24 -0700895 // If there is no thumbnail transition, but is launching from home into recents, then
Winson008ee15f2016-03-18 17:17:25 -0700896 // use a quick home transition
897 ActivityOptions opts = hasRecentTasks
898 ? getHomeTransitionActivityOptions()
899 : getUnknownTransitionActivityOptions();
Winsonc5b12dd2016-03-23 20:25:12 -0700900 startRecentsActivity(opts);
Winson190fe3bf2015-10-20 14:57:24 -0700901 }
902 mLastToggleTime = SystemClock.elapsedRealtime();
903 }
904
905 /**
906 * Starts the recents activity.
907 */
Winsonc5b12dd2016-03-23 20:25:12 -0700908 private void startRecentsActivity(ActivityOptions opts) {
Winson190fe3bf2015-10-20 14:57:24 -0700909 Intent intent = new Intent();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700910 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
Winson190fe3bf2015-10-20 14:57:24 -0700911 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
912 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
913 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800914
Winson190fe3bf2015-10-20 14:57:24 -0700915 if (opts != null) {
916 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
917 } else {
918 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
919 }
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800920 EventBus.getDefault().send(new RecentsActivityStartingEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700921 }
922
Winson3fb67562015-11-11 10:39:03 -0800923 /**** OnAnimationFinishedListener Implementation ****/
Filip Gruszczynski1a5203d2015-10-29 17:43:49 -0700924
925 @Override
926 public void onAnimationFinished() {
927 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
928 }
Winson190fe3bf2015-10-20 14:57:24 -0700929}