blob: 86b03c850da471ec4dc9daabd9592e1049703807 [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
Winson190fe3bf2015-10-20 14:57:24 -070019import android.app.ActivityManager;
20import android.app.ActivityOptions;
21import android.app.ITaskStackListener;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080022import android.app.UiModeManager;
Winsond8b1d632016-01-04 17:51:18 -080023import android.appwidget.AppWidgetProviderInfo;
Winson190fe3bf2015-10-20 14:57:24 -070024import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070025import android.content.Context;
26import android.content.Intent;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080027import android.content.res.Configuration;
Winson190fe3bf2015-10-20 14:57:24 -070028import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.Canvas;
31import android.graphics.Rect;
Winson3150e572015-10-23 15:07:24 -070032import android.graphics.RectF;
Winson190fe3bf2015-10-20 14:57:24 -070033import android.os.Handler;
34import android.os.SystemClock;
35import android.os.UserHandle;
Winson1b585612015-11-06 09:16:26 -080036import android.util.Log;
Winson190fe3bf2015-10-20 14:57:24 -070037import android.util.MutableBoolean;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -070038import android.view.AppTransitionAnimationSpec;
Winson190fe3bf2015-10-20 14:57:24 -070039import android.view.LayoutInflater;
40import android.view.View;
Winsonb61e6542016-02-04 14:37:18 -080041import android.view.ViewConfiguration;
Winsonc0d70582016-01-29 10:24:39 -080042
Winson190fe3bf2015-10-20 14:57:24 -070043import com.android.internal.logging.MetricsLogger;
44import com.android.systemui.Prefs;
45import com.android.systemui.R;
46import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070047import com.android.systemui.recents.events.EventBus;
Jorim Jaggi11cc01d2016-01-22 19:39:23 -080048import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
Winson1b585612015-11-06 09:16:26 -080049import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
Winson412e1802015-10-20 16:57:57 -070050import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070051import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Winsonb61e6542016-02-04 14:37:18 -080052import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080053import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
Winson412e1802015-10-20 16:57:57 -070054import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
Winson190fe3bf2015-10-20 14:57:24 -070055import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
56import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
Jorim Jaggidd98d412015-11-18 15:57:38 -080057import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
58import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
Winson6b92c6e2015-11-06 13:11:16 -080059import com.android.systemui.recents.misc.DozeTrigger;
Winsonab84fc52015-10-23 11:52:07 -070060import com.android.systemui.recents.misc.ForegroundThread;
Winson190fe3bf2015-10-20 14:57:24 -070061import com.android.systemui.recents.misc.SystemServicesProxy;
62import 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;
Winson190fe3bf2015-10-20 14:57:24 -070069import com.android.systemui.recents.views.TaskViewHeader;
70import com.android.systemui.recents.views.TaskViewTransform;
Winsond8b1d632016-01-04 17:51:18 -080071import com.android.systemui.statusbar.BaseStatusBar;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080072import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
Winson190fe3bf2015-10-20 14:57:24 -070073import com.android.systemui.statusbar.phone.PhoneStatusBar;
74
75import java.util.ArrayList;
76
Winson1b585612015-11-06 09:16:26 -080077import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
78
Winson190fe3bf2015-10-20 14:57:24 -070079/**
80 * An implementation of the Recents component for the current user. For secondary users, this can
81 * be called remotely from the system user.
82 */
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080083public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
Winson190fe3bf2015-10-20 14:57:24 -070084
85 private final static String TAG = "RecentsImpl";
Winsonb61e6542016-02-04 14:37:18 -080086
Winson6b92c6e2015-11-06 13:11:16 -080087 // The minimum amount of time between each recents button press that we will handle
88 private final static int MIN_TOGGLE_DELAY_MS = 350;
Winsonb61e6542016-02-04 14:37:18 -080089
Winson6b92c6e2015-11-06 13:11:16 -080090 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
91 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
92 // duration, then we will toggle recents after this duration.
93 private final static int FAST_ALT_TAB_DELAY_MS = 225;
Winson190fe3bf2015-10-20 14:57:24 -070094
95 public final static String RECENTS_PACKAGE = "com.android.systemui";
96 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080097 public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity";
Winson190fe3bf2015-10-20 14:57:24 -070098
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080099 //Used to store tv or non-tv activty for use in creating intents.
100 private final String mRecentsIntentActivityName;
Winson190fe3bf2015-10-20 14:57:24 -0700101 /**
102 * An implementation of ITaskStackListener, that allows us to listen for changes to the system
103 * task stacks and update recents accordingly.
104 */
105 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
106 Handler mHandler;
107
108 public TaskStackListenerImpl(Handler handler) {
109 mHandler = handler;
110 }
111
112 @Override
113 public void onTaskStackChanged() {
114 // Debounce any task stack changes
115 mHandler.removeCallbacks(this);
116 mHandler.post(this);
117 }
118
Wale Ogunwale03ce8632015-12-29 16:15:22 -0800119 @Override
120 public void onActivityPinned() {
121 }
122
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800123 @Override
124 public void onPinnedActivityRestartAttempt() {
125 }
126
Wale Ogunwale480dca02016-02-06 13:58:29 -0800127 @Override
128 public void onPinnedStackAnimationEnded() {
129 }
130
Winson190fe3bf2015-10-20 14:57:24 -0700131 /** Preloads the next task */
132 public void run() {
Jorim Jaggia0fdeec2016-01-07 14:42:28 +0100133 RecentsConfiguration config = Recents.getConfiguration();
Winson190fe3bf2015-10-20 14:57:24 -0700134 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winsone7f138c2015-10-22 16:15:21 -0700135 RecentsTaskLoader loader = Recents.getTaskLoader();
136 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700137 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
138
139 // Load the next task only if we aren't svelte
140 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800141 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700142 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
143 // This callback is made when a new activity is launched and the old one is paused
144 // so ignore the current activity and try and preload the thumbnail for the
145 // previous one.
146 if (runningTaskInfo != null) {
147 launchOpts.runningTaskId = runningTaskInfo.id;
148 }
149 launchOpts.numVisibleTasks = 2;
150 launchOpts.numVisibleTaskThumbnails = 2;
151 launchOpts.onlyLoadForCache = true;
152 launchOpts.onlyLoadPausedActivities = true;
153 loader.loadTasks(mContext, plan, launchOpts);
154 }
Winson190fe3bf2015-10-20 14:57:24 -0700155 }
156 }
157
Winsone7f138c2015-10-22 16:15:21 -0700158 private static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700159
160 Context mContext;
Winson190fe3bf2015-10-20 14:57:24 -0700161 Handler mHandler;
162 TaskStackListenerImpl mTaskStackListener;
163 RecentsAppWidgetHost mAppWidgetHost;
Winson190fe3bf2015-10-20 14:57:24 -0700164 boolean mCanReuseTaskStackViews = true;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800165 boolean mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800166 boolean mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700167
168 // Task launching
Winson190fe3bf2015-10-20 14:57:24 -0700169 Rect mSearchBarBounds = new Rect();
170 Rect mTaskStackBounds = new Rect();
171 Rect mLastTaskViewBounds = new Rect();
172 TaskViewTransform mTmpTransform = new TaskViewTransform();
173 int mStatusBarHeight;
174 int mNavBarHeight;
175 int mNavBarWidth;
176 int mTaskBarHeight;
177
178 // Header (for transition)
179 TaskViewHeader mHeaderBar;
180 final Object mHeaderBarLock = new Object();
181 TaskStackView mDummyStackView;
182
183 // Variables to keep track of if we need to start recents after binding
184 boolean mTriggeredFromAltTab;
185 long mLastToggleTime;
Winson6b92c6e2015-11-06 13:11:16 -0800186 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
187 @Override
188 public void run() {
189 // When this fires, then the user has not released alt-tab for at least
190 // FAST_ALT_TAB_DELAY_MS milliseconds
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800191 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
192 false /* reloadTasks */);
Winson6b92c6e2015-11-06 13:11:16 -0800193 }
194 });
Winson190fe3bf2015-10-20 14:57:24 -0700195
196 Bitmap mThumbnailTransitionBitmapCache;
197 Task mThumbnailTransitionBitmapCacheKey;
198
Winson190fe3bf2015-10-20 14:57:24 -0700199 public RecentsImpl(Context context) {
200 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700201 mHandler = new Handler();
Winson1b585612015-11-06 09:16:26 -0800202 mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
Winson190fe3bf2015-10-20 14:57:24 -0700203
Winsonab84fc52015-10-23 11:52:07 -0700204 // Initialize the static foreground thread
205 ForegroundThread.get();
206
Winson190fe3bf2015-10-20 14:57:24 -0700207 // Register the task stack listener
208 mTaskStackListener = new TaskStackListenerImpl(mHandler);
Winsone7f138c2015-10-22 16:15:21 -0700209 SystemServicesProxy ssp = Recents.getSystemServices();
210 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700211
212 // Initialize the static configuration resources
Winsonb94443d2016-01-07 15:34:13 -0800213 reloadHeaderBarLayout();
214 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700215
216 // When we start, preload the data associated with the previous recent tasks.
217 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700218 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700219 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800220 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700221 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winson Chung296278a2015-12-17 12:09:02 -0500222 launchOpts.numVisibleTasks = loader.getIconCacheSize();
Winson190fe3bf2015-10-20 14:57:24 -0700223 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
224 launchOpts.onlyLoadForCache = true;
225 loader.loadTasks(mContext, plan, launchOpts);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800226
227 //Manager used to determine if we are running on tv or not
228 UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
229 if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
230 mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
231 } else {
232 mRecentsIntentActivityName = RECENTS_ACTIVITY;
233 }
Winson190fe3bf2015-10-20 14:57:24 -0700234 }
235
236 public void onBootCompleted() {
Winsonb94443d2016-01-07 15:34:13 -0800237 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700238 }
239
Winson190fe3bf2015-10-20 14:57:24 -0700240 public void onConfigurationChanged() {
Winsonb94443d2016-01-07 15:34:13 -0800241 reloadHeaderBarLayout();
242 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700243 // Don't reuse task stack views if the configuration changes
244 mCanReuseTaskStackViews = false;
Winson53ec42c2015-10-28 15:55:35 -0700245 Recents.getConfiguration().updateOnConfigurationChange();
Winson190fe3bf2015-10-20 14:57:24 -0700246 }
247
248 /**
249 * This is only called from the system user's Recents. Secondary users will instead proxy their
250 * visibility change events through to the system user via
251 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
252 */
253 public void onVisibilityChanged(Context context, boolean visible) {
254 SystemUIApplication app = (SystemUIApplication) context;
255 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
256 if (statusBar != null) {
257 statusBar.updateRecentsVisibility(visible);
258 }
259 }
260
261 /**
262 * This is only called from the system user's Recents. Secondary users will instead proxy their
263 * visibility change events through to the system user via
264 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
265 */
266 public void onStartScreenPinning(Context context) {
267 SystemUIApplication app = (SystemUIApplication) context;
268 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
269 if (statusBar != null) {
270 statusBar.showScreenPinningRequest(false);
271 }
272 }
273
Jorim Jaggibb42a462015-11-20 16:27:16 -0800274 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
Jorim Jaggie161f082016-02-05 14:26:16 -0800275 boolean animate, boolean launchedWhileDockingTask) {
Winson190fe3bf2015-10-20 14:57:24 -0700276 mTriggeredFromAltTab = triggeredFromAltTab;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800277 mDraggingInRecents = draggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800278 mLaunchedWhileDocking = launchedWhileDockingTask;
Winson6b92c6e2015-11-06 13:11:16 -0800279 if (mFastAltTabTrigger.hasTriggered()) {
280 // We are calling this from the doze trigger, so just fall through to show Recents
281 mFastAltTabTrigger.resetTrigger();
282 } else if (mFastAltTabTrigger.isDozing()) {
283 // We are dozing but haven't yet triggered, ignore this if this is not another alt-tab,
284 // otherwise, this is an additional tab (alt-tab*), which means that we should trigger
285 // immediately (fall through and disable the pending trigger)
286 // TODO: This is tricky, we need to handle the tab key, but Recents has not yet started
287 // so we may actually additional signal to handle multiple quick tab cases. The
288 // severity of this is inversely proportional to the FAST_ALT_TAB_DELAY_MS
289 // duration though
290 if (!triggeredFromAltTab) {
291 return;
292 }
293 mFastAltTabTrigger.stopDozing();
294 } else {
295 // Otherwise, the doze trigger is not running, and if this is an alt tab, we should
296 // start the trigger and then wait for the hide (or for it to elapse)
297 if (triggeredFromAltTab) {
298 mFastAltTabTrigger.startDozing();
299 return;
300 }
301 }
Winson190fe3bf2015-10-20 14:57:24 -0700302
303 try {
304 // Check if the top task is in the home stack, and start the recents activity
Winsone7f138c2015-10-22 16:15:21 -0700305 SystemServicesProxy ssp = Recents.getSystemServices();
306 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700307 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700308 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Jorim Jaggibb42a462015-11-20 16:27:16 -0800309 startRecentsActivity(topTask, isTopTaskHome.value, animate);
Winson190fe3bf2015-10-20 14:57:24 -0700310 }
311 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800312 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700313 }
314 }
315
Winson190fe3bf2015-10-20 14:57:24 -0700316 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
Winson2799eca2016-02-25 12:10:42 -0800317 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
318 // The user has released alt-tab before the trigger has run, so just show the next
319 // task immediately
320 showNextTask();
Winson6b92c6e2015-11-06 13:11:16 -0800321
Winson2799eca2016-02-25 12:10:42 -0800322 // Cancel the fast alt-tab trigger
323 mFastAltTabTrigger.stopDozing();
324 mFastAltTabTrigger.resetTrigger();
325 return;
Winson190fe3bf2015-10-20 14:57:24 -0700326 }
Winson2799eca2016-02-25 12:10:42 -0800327
328 // Defer to the activity to handle hiding recents, if it handles it, then it must still
329 // be visible
330 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
331 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700332 }
333
Winson190fe3bf2015-10-20 14:57:24 -0700334 public void toggleRecents() {
Winson6b92c6e2015-11-06 13:11:16 -0800335 // Skip this toggle if we are already waiting to trigger recents via alt-tab
336 if (mFastAltTabTrigger.isDozing()) {
337 return;
338 }
339
Jorim Jaggidd98d412015-11-18 15:57:38 -0800340 mDraggingInRecents = false;
Jorim Jaggie161f082016-02-05 14:26:16 -0800341 mLaunchedWhileDocking = false;
Winson190fe3bf2015-10-20 14:57:24 -0700342 mTriggeredFromAltTab = false;
343
344 try {
Winsone7f138c2015-10-22 16:15:21 -0700345 SystemServicesProxy ssp = Recents.getSystemServices();
346 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700347 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsonb61e6542016-02-04 14:37:18 -0800348 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
349
Winsone7f138c2015-10-22 16:15:21 -0700350 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Winson5da43472015-11-04 17:39:55 -0800351 RecentsConfiguration config = Recents.getConfiguration();
352 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson Chungead5c0f2015-12-14 11:18:57 -0500353 if (!launchState.launchedWithAltTab) {
Winsonb61e6542016-02-04 14:37:18 -0800354 // If the user taps quickly
355 if (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
356 elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
357 // Launch the next focused task
358 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
359 } else {
360 // Notify recents to move onto the next task
361 EventBus.getDefault().post(new IterateRecentsEvent());
362 }
Winson0d14d4d2015-10-26 17:05:04 -0700363 } else {
364 // If the user has toggled it too quickly, then just eat up the event here (it's
365 // better than showing a janky screenshot).
366 // NOTE: Ideally, the screenshot mechanism would take the window transform into
367 // account
Winsonb61e6542016-02-04 14:37:18 -0800368 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700369 return;
370 }
371
372 EventBus.getDefault().post(new ToggleRecentsEvent());
373 mLastToggleTime = SystemClock.elapsedRealtime();
374 }
Winson190fe3bf2015-10-20 14:57:24 -0700375 return;
376 } else {
Winson0d14d4d2015-10-26 17:05:04 -0700377 // If the user has toggled it too quickly, then just eat up the event here (it's
378 // better than showing a janky screenshot).
379 // NOTE: Ideally, the screenshot mechanism would take the window transform into
380 // account
Winsonb61e6542016-02-04 14:37:18 -0800381 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700382 return;
383 }
384
Winson190fe3bf2015-10-20 14:57:24 -0700385 // Otherwise, start the recents activity
Jorim Jaggibb42a462015-11-20 16:27:16 -0800386 startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
Winsond8b1d632016-01-04 17:51:18 -0800387
388 // Only close the other system windows if we are actually showing recents
389 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
Winson0d14d4d2015-10-26 17:05:04 -0700390 mLastToggleTime = SystemClock.elapsedRealtime();
Winson190fe3bf2015-10-20 14:57:24 -0700391 }
392 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800393 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700394 }
395 }
396
Winson190fe3bf2015-10-20 14:57:24 -0700397 public void preloadRecents() {
398 // Preload only the raw task list into a new load plan (which will be consumed by the
399 // RecentsActivity) only if there is a task to animate to.
Winsone7f138c2015-10-22 16:15:21 -0700400 SystemServicesProxy ssp = Recents.getSystemServices();
401 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700402 MutableBoolean topTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700403 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
Winson5b4e0d22016-02-16 18:11:35 -0800404 RecentsTaskLoader loader = Recents.getTaskLoader();
405 sInstanceLoadPlan = loader.createLoadPlan(mContext);
Winson190fe3bf2015-10-20 14:57:24 -0700406 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
Winson65c851e2016-01-20 12:43:35 -0800407 loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
Winson190fe3bf2015-10-20 14:57:24 -0700408 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winson4b057c62016-01-12 15:01:52 -0800409 if (stack.getTaskCount() > 0) {
Winsonab84fc52015-10-23 11:52:07 -0700410 // We try and draw the thumbnail transition bitmap in parallel before
411 // toggle/show recents is called
Winson190fe3bf2015-10-20 14:57:24 -0700412 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
413 }
414 }
415 }
416
Winson190fe3bf2015-10-20 14:57:24 -0700417 public void cancelPreloadingRecents() {
418 // Do nothing
419 }
420
Jorim Jaggidd98d412015-11-18 15:57:38 -0800421 public void onDraggingInRecents(float distanceFromTop) {
422 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
423 }
424
Jorim Jaggidd98d412015-11-18 15:57:38 -0800425 public void onDraggingInRecentsEnded(float velocity) {
426 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
427 }
428
Winson6b92c6e2015-11-06 13:11:16 -0800429 /**
430 * Transitions to the next recent task in the stack.
431 */
432 public void showNextTask() {
Winsone7f138c2015-10-22 16:15:21 -0700433 SystemServicesProxy ssp = Recents.getSystemServices();
Winson6b92c6e2015-11-06 13:11:16 -0800434 RecentsTaskLoader loader = Recents.getTaskLoader();
435 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800436 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson6b92c6e2015-11-06 13:11:16 -0800437 TaskStack focusedStack = plan.getTaskStack();
438
439 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800440 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson6b92c6e2015-11-06 13:11:16 -0800441
442 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
443 // Return early if there is no running task
444 if (runningTask == null) return;
Winson6b92c6e2015-11-06 13:11:16 -0800445
446 // Find the task in the recents list
Winsone86deb82015-11-12 09:32:10 -0800447 boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
Winson250608a2015-11-24 15:00:31 -0800448 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson6b92c6e2015-11-06 13:11:16 -0800449 Task toTask = null;
450 ActivityOptions launchOpts = null;
451 int taskCount = tasks.size();
452 for (int i = taskCount - 1; i >= 1; i--) {
453 Task task = tasks.get(i);
Winsone86deb82015-11-12 09:32:10 -0800454 if (isTopTaskHome) {
455 toTask = tasks.get(i - 1);
456 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
457 R.anim.recents_launch_next_affiliated_task_target,
458 R.anim.recents_fast_toggle_app_home_exit);
459 break;
460 } else if (task.key.id == runningTask.id) {
Winson6b92c6e2015-11-06 13:11:16 -0800461 toTask = tasks.get(i - 1);
462 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
463 R.anim.recents_launch_prev_affiliated_task_target,
464 R.anim.recents_launch_prev_affiliated_task_source);
465 break;
466 }
467 }
468
469 // Return early if there is no next task
470 if (toTask == null) {
471 ssp.startInPlaceAnimationOnFrontMostApplication(
472 ActivityOptions.makeCustomInPlaceAnimation(mContext,
473 R.anim.recents_launch_prev_affiliated_task_bounce));
474 return;
475 }
476
477 // Launch the task
Winson Chung296278a2015-12-17 12:09:02 -0500478 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
Winson6b92c6e2015-11-06 13:11:16 -0800479 }
480
481 /**
482 * Transitions to the next affiliated task.
483 */
484 public void showRelativeAffiliatedTask(boolean showNextTask) {
485 SystemServicesProxy ssp = Recents.getSystemServices();
Winsone7f138c2015-10-22 16:15:21 -0700486 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700487 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800488 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700489 TaskStack focusedStack = plan.getTaskStack();
490
491 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800492 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson190fe3bf2015-10-20 14:57:24 -0700493
Winsone7f138c2015-10-22 16:15:21 -0700494 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700495 // Return early if there is no running task (can't determine affiliated tasks in this case)
496 if (runningTask == null) return;
497 // Return early if the running task is in the home stack (optimization)
Winson5510f6c2015-10-27 12:11:26 -0700498 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
Winson190fe3bf2015-10-20 14:57:24 -0700499
500 // Find the task in the recents list
Winson250608a2015-11-24 15:00:31 -0800501 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson190fe3bf2015-10-20 14:57:24 -0700502 Task toTask = null;
503 ActivityOptions launchOpts = null;
504 int taskCount = tasks.size();
505 int numAffiliatedTasks = 0;
506 for (int i = 0; i < taskCount; i++) {
507 Task task = tasks.get(i);
508 if (task.key.id == runningTask.id) {
509 TaskGrouping group = task.group;
510 Task.TaskKey toTaskKey;
511 if (showNextTask) {
512 toTaskKey = group.getNextTaskInGroup(task);
513 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
514 R.anim.recents_launch_next_affiliated_task_target,
515 R.anim.recents_launch_next_affiliated_task_source);
516 } else {
517 toTaskKey = group.getPrevTaskInGroup(task);
518 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
519 R.anim.recents_launch_prev_affiliated_task_target,
520 R.anim.recents_launch_prev_affiliated_task_source);
521 }
522 if (toTaskKey != null) {
523 toTask = focusedStack.findTaskWithId(toTaskKey.id);
524 }
525 numAffiliatedTasks = group.getTaskCount();
526 break;
527 }
528 }
529
530 // Return early if there is no next task
531 if (toTask == null) {
532 if (numAffiliatedTasks > 1) {
533 if (showNextTask) {
Winsone7f138c2015-10-22 16:15:21 -0700534 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700535 ActivityOptions.makeCustomInPlaceAnimation(mContext,
536 R.anim.recents_launch_next_affiliated_task_bounce));
537 } else {
Winsone7f138c2015-10-22 16:15:21 -0700538 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700539 ActivityOptions.makeCustomInPlaceAnimation(mContext,
540 R.anim.recents_launch_prev_affiliated_task_bounce));
541 }
542 }
543 return;
544 }
545
546 // Keep track of actually launched affiliated tasks
547 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
548
549 // Launch the task
Winson Chung296278a2015-12-17 12:09:02 -0500550 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700551 }
552
553 public void showNextAffiliatedTask() {
554 // Keep track of when the affiliated task is triggered
555 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
556 showRelativeAffiliatedTask(true);
557 }
558
559 public void showPrevAffiliatedTask() {
560 // Keep track of when the affiliated task is triggered
561 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
562 showRelativeAffiliatedTask(false);
563 }
564
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800565 public void dockTopTask(int topTaskId, int dragMode,
566 int stackCreateMode, Rect initialBounds) {
Jorim Jaggi75b25972015-10-21 14:51:10 +0200567 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggi9511b0f2016-01-29 19:12:44 -0800568
569 // Make sure we inform DividerView before we actually start the activity so we can change
570 // the resize mode already.
Chong Zhange4fbd322016-03-01 14:44:03 -0800571 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
572 EventBus.getDefault().send(new DockingTopTaskEvent(dragMode));
573 showRecents(
574 false /* triggeredFromAltTab */,
575 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
576 false /* animate */,
577 true /* reloadTasks*/);
578 }
Jorim Jaggi75b25972015-10-21 14:51:10 +0200579 }
580
Winson190fe3bf2015-10-20 14:57:24 -0700581 /**
582 * Returns the preloaded load plan and invalidates it.
583 */
584 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
585 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
586 sInstanceLoadPlan = null;
587 return plan;
588 }
589
590 /**
Winsonb94443d2016-01-07 15:34:13 -0800591 * Reloads all the layouts for the header bar transition.
592 */
593 private void reloadHeaderBarLayout() {
594 Resources res = mContext.getResources();
595 LayoutInflater inflater = LayoutInflater.from(mContext);
596
597 mStatusBarHeight = res.getDimensionPixelSize(
598 com.android.internal.R.dimen.status_bar_height);
599 mNavBarHeight = res.getDimensionPixelSize(
600 com.android.internal.R.dimen.navigation_bar_height);
601 mNavBarWidth = res.getDimensionPixelSize(
602 com.android.internal.R.dimen.navigation_bar_width);
603 mTaskBarHeight = res.getDimensionPixelSize(
604 R.dimen.recents_task_bar_height);
605 mDummyStackView = new TaskStackView(mContext, new TaskStack());
606 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
607 null, false);
608 }
609
610 /**
Winson190fe3bf2015-10-20 14:57:24 -0700611 * Prepares the header bar layout for the next transition, if the task view bounds has changed
612 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
613 *
614 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
615 * is not already bound (can be expensive)
Winsonf0d1c442015-12-01 11:04:45 -0800616 * @param stack the stack to initialize the stack layout with
Winson190fe3bf2015-10-20 14:57:24 -0700617 */
Winsonb94443d2016-01-07 15:34:13 -0800618 private void updateHeaderBarLayout(boolean tryAndBindSearchWidget,
619 TaskStack stack) {
Winson53ec42c2015-10-28 15:55:35 -0700620 RecentsConfiguration config = Recents.getConfiguration();
Winsone7f138c2015-10-22 16:15:21 -0700621 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800622 Rect systemInsets = new Rect();
623 ssp.getStableInsets(systemInsets);
Winsone7f138c2015-10-22 16:15:21 -0700624 Rect windowRect = ssp.getWindowRect();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800625 calculateWindowStableInsets(systemInsets, windowRect);
626 windowRect.offsetTo(0, 0);
Winson190fe3bf2015-10-20 14:57:24 -0700627
628 // Update the configuration for the current state
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800629 config.update(systemInsets);
Winson190fe3bf2015-10-20 14:57:24 -0700630
Winsond8b1d632016-01-04 17:51:18 -0800631 if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
Winson190fe3bf2015-10-20 14:57:24 -0700632 // Try and pre-emptively bind the search widget on startup to ensure that we
633 // have the right thumbnail bounds to animate to.
634 // Note: We have to reload the widget id before we get the task stack bounds below
Winsone7f138c2015-10-22 16:15:21 -0700635 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
Winson53ec42c2015-10-28 15:55:35 -0700636 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
Winson190fe3bf2015-10-20 14:57:24 -0700637 }
638 }
Winson53ec42c2015-10-28 15:55:35 -0700639 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
Winson190fe3bf2015-10-20 14:57:24 -0700640 mSearchBarBounds, mTaskStackBounds);
641
642 // Rebind the header bar and draw it for the transition
Winson36a5a2c2015-10-29 18:04:39 -0700643 TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Winson190fe3bf2015-10-20 14:57:24 -0700644 Rect taskStackBounds = new Rect(mTaskStackBounds);
645 algo.setSystemInsets(systemInsets);
Winsonf0d1c442015-12-01 11:04:45 -0800646 if (stack != null) {
647 algo.initialize(taskStackBounds,
648 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
649 }
Winson190fe3bf2015-10-20 14:57:24 -0700650 Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
651 if (!taskViewBounds.equals(mLastTaskViewBounds)) {
652 mLastTaskViewBounds.set(taskViewBounds);
653
654 int taskViewWidth = taskViewBounds.width();
655 synchronized (mHeaderBarLock) {
656 mHeaderBar.measure(
657 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
658 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
659 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
660 }
661 }
662 }
663
664 /**
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800665 * Given the stable insets and the rect for our window, calculates the insets that affect our
666 * window.
667 */
668 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
669 Rect displayRect = Recents.getSystemServices().getDisplayRect();
670
671 // Display rect without insets - available app space
672 Rect appRect = new Rect(displayRect);
673 appRect.inset(inOutInsets);
674
675 // Our window intersected with available app space
676 Rect windowRectWithInsets = new Rect(windowRect);
677 windowRectWithInsets.intersect(appRect);
678 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
679 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
680 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
681 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
682 }
683
684 /**
Winson190fe3bf2015-10-20 14:57:24 -0700685 * Preloads the icon of a task.
686 */
687 private void preloadIcon(ActivityManager.RunningTaskInfo task) {
Winson190fe3bf2015-10-20 14:57:24 -0700688 // Ensure that we load the running task's icon
689 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
690 launchOpts.runningTaskId = task.id;
691 launchOpts.loadThumbnails = false;
692 launchOpts.onlyLoadForCache = true;
Winsone7f138c2015-10-22 16:15:21 -0700693 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700694 }
695
696 /**
697 * Caches the header thumbnail used for a window animation asynchronously into
698 * {@link #mThumbnailTransitionBitmapCache}.
699 */
700 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
701 TaskStack stack, TaskStackView stackView) {
702 preloadIcon(topTask);
703
704 // Update the header bar if necessary
Winsonb94443d2016-01-07 15:34:13 -0800705 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
Winson190fe3bf2015-10-20 14:57:24 -0700706
707 // Update the destination rect
Winsoneca4ab62015-11-04 10:50:28 -0800708 mDummyStackView.updateLayoutForStack(stack);
Winson190fe3bf2015-10-20 14:57:24 -0700709 final Task toTask = new Task();
710 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
Winson35a8b042016-01-22 09:41:09 -0800711 toTask);
Winson3fb67562015-11-11 10:39:03 -0800712 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
Winson190fe3bf2015-10-20 14:57:24 -0700713 @Override
Winsonab84fc52015-10-23 11:52:07 -0700714 public void run() {
715 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
716 mHandler.post(new Runnable() {
717 @Override
718 public void run() {
719 mThumbnailTransitionBitmapCache = transitionBitmap;
720 mThumbnailTransitionBitmapCacheKey = toTask;
721 }
722 });
Winson190fe3bf2015-10-20 14:57:24 -0700723 }
Winsonab84fc52015-10-23 11:52:07 -0700724 });
Winson190fe3bf2015-10-20 14:57:24 -0700725 }
726
727 /**
728 * Creates the activity options for a unknown state->recents transition.
729 */
730 private ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700731 return ActivityOptions.makeCustomAnimation(mContext,
732 R.anim.recents_from_unknown_enter,
733 R.anim.recents_from_unknown_exit,
Winson3fb67562015-11-11 10:39:03 -0800734 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700735 }
736
737 /**
738 * Creates the activity options for a home->recents transition.
739 */
740 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
Winson190fe3bf2015-10-20 14:57:24 -0700741 if (fromSearchHome) {
742 return ActivityOptions.makeCustomAnimation(mContext,
743 R.anim.recents_from_search_launcher_enter,
744 R.anim.recents_from_search_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800745 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700746 }
747 return ActivityOptions.makeCustomAnimation(mContext,
748 R.anim.recents_from_launcher_enter,
749 R.anim.recents_from_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800750 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700751 }
752
753 /**
754 * Creates the activity options for an app->recents transition.
755 */
756 private ActivityOptions getThumbnailTransitionActivityOptions(
757 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700758 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
759 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
760 stackView.getScroller().setStackScrollToInitialState();
Winson250608a2015-11-24 15:00:31 -0800761 ArrayList<Task> tasks = stack.getStackTasks();
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700762 for (int i = tasks.size() - 1; i >= 0; i--) {
763 Task task = tasks.get(i);
Winson387aac62015-11-25 11:18:56 -0800764 if (task.isFreeformTask()) {
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800765 mTmpTransform = stackView.getStackAlgorithm()
766 .getStackTransformScreenCoordinates(task,
767 stackView.getScroller().getStackScroll(), mTmpTransform, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700768 Rect toTaskRect = new Rect();
769 mTmpTransform.rect.round(toTaskRect);
770 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
771 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
772 }
773 }
774 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
775 specs.toArray(specsArray);
776 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3fb67562015-11-11 10:39:03 -0800777 specsArray, mHandler, null, this);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700778 } else {
779 // Update the destination rect
780 Task toTask = new Task();
781 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
Winson35a8b042016-01-22 09:41:09 -0800782 toTask);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700783 RectF toTaskRect = toTransform.rect;
784 Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
785 if (thumbnail != null) {
786 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
787 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
Winson3fb67562015-11-11 10:39:03 -0800788 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700789 }
790 // If both the screenshot and thumbnail fails, then just fall back to the default transition
791 return getUnknownTransitionActivityOptions();
792 }
793 }
Winson190fe3bf2015-10-20 14:57:24 -0700794
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700795 private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
796 TaskViewTransform toTransform) {
Winson190fe3bf2015-10-20 14:57:24 -0700797 Bitmap thumbnail;
798 if (mThumbnailTransitionBitmapCacheKey != null
799 && mThumbnailTransitionBitmapCacheKey.key != null
800 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
801 thumbnail = mThumbnailTransitionBitmapCache;
802 mThumbnailTransitionBitmapCacheKey = null;
803 mThumbnailTransitionBitmapCache = null;
804 } else {
805 preloadIcon(topTask);
806 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
807 }
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700808 return thumbnail;
Winson190fe3bf2015-10-20 14:57:24 -0700809 }
810
811 /**
812 * Returns the transition rect for the given task id.
813 */
814 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
Winson35a8b042016-01-22 09:41:09 -0800815 TaskStackView stackView, Task runningTaskOut) {
Winson190fe3bf2015-10-20 14:57:24 -0700816 // Find the running task in the TaskStack
Winson65c851e2016-01-20 12:43:35 -0800817 Task launchTask = stack.getLaunchTarget();
818 if (launchTask != null) {
819 runningTaskOut.copyFrom(launchTask);
820 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700821 // If no task is specified or we can not find the task just use the front most one
Winson35a8b042016-01-22 09:41:09 -0800822 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
Winson65c851e2016-01-20 12:43:35 -0800823 runningTaskOut.copyFrom(launchTask);
Winson190fe3bf2015-10-20 14:57:24 -0700824 }
825
826 // Get the transform for the running task
827 stackView.getScroller().setStackScrollToInitialState();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800828 mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
Winson190fe3bf2015-10-20 14:57:24 -0700829 stackView.getScroller().getStackScroll(), mTmpTransform, null);
830 return mTmpTransform;
831 }
832
833 /**
834 * Draws the header of a task used for the window animation into a bitmap.
835 */
836 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
Winson8be16342016-02-09 11:53:27 -0800837 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700838 if (toTransform != null && toTask.key != null) {
839 Bitmap thumbnail;
840 synchronized (mHeaderBarLock) {
Winson Chung509d0d02015-12-16 15:43:12 -0500841 int toHeaderWidth = (int) toTransform.rect.width();
Winson190fe3bf2015-10-20 14:57:24 -0700842 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
Winson8be16342016-02-09 11:53:27 -0800843 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
Winson Chung509d0d02015-12-16 15:43:12 -0500844 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
845 (int) toTransform.rect.height());
Winson190fe3bf2015-10-20 14:57:24 -0700846 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
847 Bitmap.Config.ARGB_8888);
Winsonc742f972015-11-12 11:32:21 -0800848 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson190fe3bf2015-10-20 14:57:24 -0700849 thumbnail.eraseColor(0xFFff0000);
850 } else {
851 Canvas c = new Canvas(thumbnail);
852 c.scale(toTransform.scale, toTransform.scale);
Winson8be16342016-02-09 11:53:27 -0800853 mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
854 disabledInSafeMode);
Winson190fe3bf2015-10-20 14:57:24 -0700855 mHeaderBar.draw(c);
856 c.setBitmap(null);
857 }
858 }
859 return thumbnail.createAshmemBitmap();
860 }
861 return null;
862 }
863
864 /**
865 * Shows the recents activity
866 */
867 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
Jorim Jaggibb42a462015-11-20 16:27:16 -0800868 boolean isTopTaskHome, boolean animate) {
Winsone7f138c2015-10-22 16:15:21 -0700869 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700870
Winsone5f1faa2015-11-20 12:26:23 -0800871 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800872 // should always preload the tasks now. If we are dragging in recents, reload them as
873 // the stacks might have changed.
Jorim Jaggie161f082016-02-05 14:26:16 -0800874 if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
Winsone5f1faa2015-11-20 12:26:23 -0800875 // Create a new load plan if preloadRecents() was never triggered
Winson190fe3bf2015-10-20 14:57:24 -0700876 sInstanceLoadPlan = loader.createLoadPlan(mContext);
877 }
Jorim Jaggie161f082016-02-05 14:26:16 -0800878 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
Winson65c851e2016-01-20 12:43:35 -0800879 loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
Winson190fe3bf2015-10-20 14:57:24 -0700880 }
881 TaskStack stack = sInstanceLoadPlan.getTaskStack();
882
Winsonf0d1c442015-12-01 11:04:45 -0800883 // Update the header bar if necessary
Winsonb94443d2016-01-07 15:34:13 -0800884 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
Winsonf0d1c442015-12-01 11:04:45 -0800885
Winson190fe3bf2015-10-20 14:57:24 -0700886 // Prepare the dummy stack for the transition
Winsoneca4ab62015-11-04 10:50:28 -0800887 mDummyStackView.updateLayoutForStack(stack);
Winson36a5a2c2015-10-29 18:04:39 -0700888 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
Winson190fe3bf2015-10-20 14:57:24 -0700889 mDummyStackView.computeStackVisibilityReport();
Jorim Jaggibb42a462015-11-20 16:27:16 -0800890
891 if (!animate) {
892 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
893 startRecentsActivity(topTask, opts, false /* fromHome */,
894 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
895 return;
896 }
897
Winson4b057c62016-01-12 15:01:52 -0800898 boolean hasRecentTasks = stack.getTaskCount() > 0;
Winson190fe3bf2015-10-20 14:57:24 -0700899 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
900
901 if (useThumbnailTransition) {
902 // Try starting with a thumbnail transition
903 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
904 mDummyStackView);
905 if (opts != null) {
906 startRecentsActivity(topTask, opts, false /* fromHome */,
907 false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
908 } else {
909 // Fall through below to the non-thumbnail transition
910 useThumbnailTransition = false;
911 }
912 }
913
914 if (!useThumbnailTransition) {
915 // If there is no thumbnail transition, but is launching from home into recents, then
916 // use a quick home transition and do the animation from home
Winsond8b1d632016-01-04 17:51:18 -0800917 if (hasRecentTasks) {
Winson5da43472015-11-04 17:39:55 -0800918 SystemServicesProxy ssp = Recents.getSystemServices();
Winsone7f138c2015-10-22 16:15:21 -0700919 String homeActivityPackage = ssp.getHomeActivityPackageName();
Winsond8b1d632016-01-04 17:51:18 -0800920 String searchWidgetPackage = null;
921 if (RecentsDebugFlags.Static.EnableSearchBar) {
922 searchWidgetPackage = Prefs.getString(mContext,
923 Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
924 } else {
925 AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
926 if (searchWidgetInfo != null) {
927 searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
928 }
929 }
Winson190fe3bf2015-10-20 14:57:24 -0700930
931 // Determine whether we are coming from a search owned home activity
932 boolean fromSearchHome = (homeActivityPackage != null) &&
933 homeActivityPackage.equals(searchWidgetPackage);
934 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
935 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
936 false /* fromThumbnail */, stackVr);
937 } else {
938 // Otherwise we do the normal fade from an unknown source
939 ActivityOptions opts = getUnknownTransitionActivityOptions();
940 startRecentsActivity(topTask, opts, true /* fromHome */,
941 false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
942 }
943 }
944 mLastToggleTime = SystemClock.elapsedRealtime();
945 }
946
947 /**
948 * Starts the recents activity.
949 */
950 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
951 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
Winson36a5a2c2015-10-29 18:04:39 -0700952 TaskStackLayoutAlgorithm.VisibilityReport vr) {
Winson190fe3bf2015-10-20 14:57:24 -0700953 // Update the configuration based on the launch options
Winson53ec42c2015-10-28 15:55:35 -0700954 RecentsConfiguration config = Recents.getConfiguration();
955 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson190fe3bf2015-10-20 14:57:24 -0700956 launchState.launchedFromHome = fromSearchHome || fromHome;
957 launchState.launchedFromSearchHome = fromSearchHome;
958 launchState.launchedFromAppWithThumbnail = fromThumbnail;
959 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
960 launchState.launchedWithAltTab = mTriggeredFromAltTab;
961 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
962 launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
963 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
964 launchState.launchedHasConfigurationChanged = false;
Winsonb1e71d02015-11-23 12:40:23 -0800965 launchState.launchedViaDragGesture = mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800966 launchState.launchedWhileDocking = mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700967
968 Intent intent = new Intent();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800969 intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
Winson190fe3bf2015-10-20 14:57:24 -0700970 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
971 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
972 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800973
Winson190fe3bf2015-10-20 14:57:24 -0700974 if (opts != null) {
975 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
976 } else {
977 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
978 }
979 mCanReuseTaskStackViews = true;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800980 EventBus.getDefault().send(new RecentsActivityStartingEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700981 }
982
Winson3fb67562015-11-11 10:39:03 -0800983 /**** OnAnimationFinishedListener Implementation ****/
Filip Gruszczynski1a5203d2015-10-29 17:43:49 -0700984
985 @Override
986 public void onAnimationFinished() {
987 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
988 }
Winson190fe3bf2015-10-20 14:57:24 -0700989}