blob: ac23f446cdc46b56ddcefe8dcbf1012e4c578fbb [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;
23import android.app.ITaskStackListener;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080024import android.app.UiModeManager;
Winsond8b1d632016-01-04 17:51:18 -080025import android.appwidget.AppWidgetProviderInfo;
Winson190fe3bf2015-10-20 14:57:24 -070026import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070027import android.content.Context;
28import android.content.Intent;
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080029import android.content.res.Configuration;
Winson190fe3bf2015-10-20 14:57:24 -070030import android.content.res.Resources;
31import android.graphics.Bitmap;
32import android.graphics.Canvas;
33import android.graphics.Rect;
Winson3150e572015-10-23 15:07:24 -070034import android.graphics.RectF;
Winson190fe3bf2015-10-20 14:57:24 -070035import android.os.Handler;
36import android.os.SystemClock;
37import android.os.UserHandle;
Winson1b585612015-11-06 09:16:26 -080038import android.util.Log;
Winson190fe3bf2015-10-20 14:57:24 -070039import android.util.MutableBoolean;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -070040import android.view.AppTransitionAnimationSpec;
Winson190fe3bf2015-10-20 14:57:24 -070041import android.view.LayoutInflater;
42import android.view.View;
Winsonb61e6542016-02-04 14:37:18 -080043import android.view.ViewConfiguration;
Winsonc0d70582016-01-29 10:24:39 -080044
Winson190fe3bf2015-10-20 14:57:24 -070045import com.android.internal.logging.MetricsLogger;
46import com.android.systemui.Prefs;
47import com.android.systemui.R;
48import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070049import com.android.systemui.recents.events.EventBus;
Jorim Jaggi11cc01d2016-01-22 19:39:23 -080050import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
Winson1b585612015-11-06 09:16:26 -080051import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
Winson412e1802015-10-20 16:57:57 -070052import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070053import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Winsonb61e6542016-02-04 14:37:18 -080054import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080055import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
Winson412e1802015-10-20 16:57:57 -070056import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
Winson190fe3bf2015-10-20 14:57:24 -070057import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
58import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
Jorim Jaggidd98d412015-11-18 15:57:38 -080059import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
60import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
Winson6b92c6e2015-11-06 13:11:16 -080061import com.android.systemui.recents.misc.DozeTrigger;
Winsonab84fc52015-10-23 11:52:07 -070062import com.android.systemui.recents.misc.ForegroundThread;
Winson190fe3bf2015-10-20 14:57:24 -070063import com.android.systemui.recents.misc.SystemServicesProxy;
64import com.android.systemui.recents.model.RecentsTaskLoadPlan;
65import com.android.systemui.recents.model.RecentsTaskLoader;
66import com.android.systemui.recents.model.Task;
67import com.android.systemui.recents.model.TaskGrouping;
68import com.android.systemui.recents.model.TaskStack;
Sid Soundararajan1008cc22016-02-01 11:11:14 -080069import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView;
Winson36a5a2c2015-10-29 18:04:39 -070070import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
Winson1b585612015-11-06 09:16:26 -080071import com.android.systemui.recents.views.TaskStackView;
Winsone693aaf2016-03-01 12:05:59 -080072import com.android.systemui.recents.views.TaskStackViewScroller;
Winson190fe3bf2015-10-20 14:57:24 -070073import com.android.systemui.recents.views.TaskViewHeader;
74import com.android.systemui.recents.views.TaskViewTransform;
Winsond8b1d632016-01-04 17:51:18 -080075import com.android.systemui.statusbar.BaseStatusBar;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080076import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
Winson190fe3bf2015-10-20 14:57:24 -070077import com.android.systemui.statusbar.phone.PhoneStatusBar;
78
79import java.util.ArrayList;
80
81/**
82 * An implementation of the Recents component for the current user. For secondary users, this can
83 * be called remotely from the system user.
84 */
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080085public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
Winson190fe3bf2015-10-20 14:57:24 -070086
87 private final static String TAG = "RecentsImpl";
Winsonb61e6542016-02-04 14:37:18 -080088
Winson6b92c6e2015-11-06 13:11:16 -080089 // The minimum amount of time between each recents button press that we will handle
90 private final static int MIN_TOGGLE_DELAY_MS = 350;
Winsonb61e6542016-02-04 14:37:18 -080091
Winson6b92c6e2015-11-06 13:11:16 -080092 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
93 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
94 // duration, then we will toggle recents after this duration.
95 private final static int FAST_ALT_TAB_DELAY_MS = 225;
Winson190fe3bf2015-10-20 14:57:24 -070096
97 public final static String RECENTS_PACKAGE = "com.android.systemui";
98 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
Sid Soundararajanb58c46a2016-01-26 15:39:27 -080099 public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity";
Winson190fe3bf2015-10-20 14:57:24 -0700100
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800101 //Used to store tv or non-tv activty for use in creating intents.
102 private final String mRecentsIntentActivityName;
Winsone693aaf2016-03-01 12:05:59 -0800103
Winson190fe3bf2015-10-20 14:57:24 -0700104 /**
105 * An implementation of ITaskStackListener, that allows us to listen for changes to the system
106 * task stacks and update recents accordingly.
107 */
108 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
109 Handler mHandler;
110
111 public TaskStackListenerImpl(Handler handler) {
112 mHandler = handler;
113 }
114
115 @Override
116 public void onTaskStackChanged() {
117 // Debounce any task stack changes
118 mHandler.removeCallbacks(this);
119 mHandler.post(this);
120 }
121
Wale Ogunwale03ce8632015-12-29 16:15:22 -0800122 @Override
123 public void onActivityPinned() {
124 }
125
Wale Ogunwalecc25a8a2016-01-23 14:31:37 -0800126 @Override
127 public void onPinnedActivityRestartAttempt() {
128 }
129
Wale Ogunwale480dca02016-02-06 13:58:29 -0800130 @Override
131 public void onPinnedStackAnimationEnded() {
132 }
133
Winson190fe3bf2015-10-20 14:57:24 -0700134 /** Preloads the next task */
135 public void run() {
Jorim Jaggia0fdeec2016-01-07 14:42:28 +0100136 RecentsConfiguration config = Recents.getConfiguration();
Winson190fe3bf2015-10-20 14:57:24 -0700137 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winsone7f138c2015-10-22 16:15:21 -0700138 RecentsTaskLoader loader = Recents.getTaskLoader();
139 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700140 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
141
142 // Load the next task only if we aren't svelte
143 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800144 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700145 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
146 // This callback is made when a new activity is launched and the old one is paused
147 // so ignore the current activity and try and preload the thumbnail for the
148 // previous one.
149 if (runningTaskInfo != null) {
150 launchOpts.runningTaskId = runningTaskInfo.id;
151 }
152 launchOpts.numVisibleTasks = 2;
153 launchOpts.numVisibleTaskThumbnails = 2;
154 launchOpts.onlyLoadForCache = true;
155 launchOpts.onlyLoadPausedActivities = true;
156 loader.loadTasks(mContext, plan, launchOpts);
157 }
Winson190fe3bf2015-10-20 14:57:24 -0700158 }
159 }
160
Winsone7f138c2015-10-22 16:15:21 -0700161 private static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700162
163 Context mContext;
Winson190fe3bf2015-10-20 14:57:24 -0700164 Handler mHandler;
165 TaskStackListenerImpl mTaskStackListener;
166 RecentsAppWidgetHost mAppWidgetHost;
Winson190fe3bf2015-10-20 14:57:24 -0700167 boolean mCanReuseTaskStackViews = true;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800168 boolean mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800169 boolean mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700170
171 // Task launching
Winson190fe3bf2015-10-20 14:57:24 -0700172 Rect mSearchBarBounds = new Rect();
173 Rect mTaskStackBounds = new Rect();
174 Rect mLastTaskViewBounds = new Rect();
175 TaskViewTransform mTmpTransform = new TaskViewTransform();
176 int mStatusBarHeight;
177 int mNavBarHeight;
178 int mNavBarWidth;
179 int mTaskBarHeight;
180
181 // Header (for transition)
182 TaskViewHeader mHeaderBar;
183 final Object mHeaderBarLock = new Object();
184 TaskStackView mDummyStackView;
185
186 // Variables to keep track of if we need to start recents after binding
187 boolean mTriggeredFromAltTab;
188 long mLastToggleTime;
Winson6b92c6e2015-11-06 13:11:16 -0800189 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
190 @Override
191 public void run() {
192 // When this fires, then the user has not released alt-tab for at least
193 // FAST_ALT_TAB_DELAY_MS milliseconds
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800194 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
195 false /* reloadTasks */);
Winson6b92c6e2015-11-06 13:11:16 -0800196 }
197 });
Winson190fe3bf2015-10-20 14:57:24 -0700198
199 Bitmap mThumbnailTransitionBitmapCache;
200 Task mThumbnailTransitionBitmapCacheKey;
201
Winson190fe3bf2015-10-20 14:57:24 -0700202 public RecentsImpl(Context context) {
203 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700204 mHandler = new Handler();
Winson1b585612015-11-06 09:16:26 -0800205 mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
Winson190fe3bf2015-10-20 14:57:24 -0700206
Winsonab84fc52015-10-23 11:52:07 -0700207 // Initialize the static foreground thread
208 ForegroundThread.get();
209
Winson190fe3bf2015-10-20 14:57:24 -0700210 // Register the task stack listener
211 mTaskStackListener = new TaskStackListenerImpl(mHandler);
Winsone7f138c2015-10-22 16:15:21 -0700212 SystemServicesProxy ssp = Recents.getSystemServices();
213 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700214
215 // Initialize the static configuration resources
Winsonb94443d2016-01-07 15:34:13 -0800216 reloadHeaderBarLayout();
217 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700218
219 // When we start, preload the data associated with the previous recent tasks.
220 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700221 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700222 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winson65c851e2016-01-20 12:43:35 -0800223 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
Winson190fe3bf2015-10-20 14:57:24 -0700224 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winson Chung296278a2015-12-17 12:09:02 -0500225 launchOpts.numVisibleTasks = loader.getIconCacheSize();
Winson190fe3bf2015-10-20 14:57:24 -0700226 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
227 launchOpts.onlyLoadForCache = true;
228 loader.loadTasks(mContext, plan, launchOpts);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800229
230 //Manager used to determine if we are running on tv or not
231 UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
232 if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
233 mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
234 } else {
235 mRecentsIntentActivityName = RECENTS_ACTIVITY;
236 }
Winson190fe3bf2015-10-20 14:57:24 -0700237 }
238
239 public void onBootCompleted() {
Winsonb94443d2016-01-07 15:34:13 -0800240 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700241 }
242
Winson190fe3bf2015-10-20 14:57:24 -0700243 public void onConfigurationChanged() {
Winsonb94443d2016-01-07 15:34:13 -0800244 reloadHeaderBarLayout();
245 updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
Winson190fe3bf2015-10-20 14:57:24 -0700246 // Don't reuse task stack views if the configuration changes
247 mCanReuseTaskStackViews = false;
Winson53ec42c2015-10-28 15:55:35 -0700248 Recents.getConfiguration().updateOnConfigurationChange();
Winson190fe3bf2015-10-20 14:57:24 -0700249 }
250
251 /**
252 * This is only called from the system user's Recents. Secondary users will instead proxy their
253 * visibility change events through to the system user via
254 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
255 */
256 public void onVisibilityChanged(Context context, boolean visible) {
257 SystemUIApplication app = (SystemUIApplication) context;
258 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
259 if (statusBar != null) {
260 statusBar.updateRecentsVisibility(visible);
261 }
262 }
263
264 /**
265 * This is only called from the system user's Recents. Secondary users will instead proxy their
266 * visibility change events through to the system user via
267 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
268 */
269 public void onStartScreenPinning(Context context) {
270 SystemUIApplication app = (SystemUIApplication) context;
271 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
272 if (statusBar != null) {
273 statusBar.showScreenPinningRequest(false);
274 }
275 }
276
Jorim Jaggibb42a462015-11-20 16:27:16 -0800277 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
Jorim Jaggie161f082016-02-05 14:26:16 -0800278 boolean animate, boolean launchedWhileDockingTask) {
Winson190fe3bf2015-10-20 14:57:24 -0700279 mTriggeredFromAltTab = triggeredFromAltTab;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800280 mDraggingInRecents = draggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800281 mLaunchedWhileDocking = launchedWhileDockingTask;
Winsone693aaf2016-03-01 12:05:59 -0800282 if (mFastAltTabTrigger.isAsleep()) {
283 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
284 mFastAltTabTrigger.stopDozing();
Winson6b92c6e2015-11-06 13:11:16 -0800285 } else if (mFastAltTabTrigger.isDozing()) {
Winsone693aaf2016-03-01 12:05:59 -0800286 // Fast alt-tab duration has not elapsed. If this is triggered by a different
287 // showRecents() call, then ignore that call for now.
288 // TODO: We can not handle quick tabs that happen between the initial showRecents() call
289 // that started the activity and the activity starting up. The severity of this
290 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
Winson6b92c6e2015-11-06 13:11:16 -0800291 if (!triggeredFromAltTab) {
292 return;
293 }
294 mFastAltTabTrigger.stopDozing();
Winsone693aaf2016-03-01 12:05:59 -0800295 } else if (triggeredFromAltTab) {
296 // The fast alt-tab detector is not yet running, so start the trigger and wait for the
297 // hideRecents() call, or for the fast alt-tab duration to elapse
298 mFastAltTabTrigger.startDozing();
299 return;
Winson6b92c6e2015-11-06 13:11:16 -0800300 }
Winson190fe3bf2015-10-20 14:57:24 -0700301
302 try {
303 // Check if the top task is in the home stack, and start the recents activity
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);
Winsone7f138c2015-10-22 16:15:21 -0700307 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Jorim Jaggibb42a462015-11-20 16:27:16 -0800308 startRecentsActivity(topTask, isTopTaskHome.value, animate);
Winson190fe3bf2015-10-20 14:57:24 -0700309 }
310 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800311 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700312 }
313 }
314
Winson190fe3bf2015-10-20 14:57:24 -0700315 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
Winson2799eca2016-02-25 12:10:42 -0800316 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
317 // The user has released alt-tab before the trigger has run, so just show the next
318 // task immediately
319 showNextTask();
Winson6b92c6e2015-11-06 13:11:16 -0800320
Winson2799eca2016-02-25 12:10:42 -0800321 // Cancel the fast alt-tab trigger
322 mFastAltTabTrigger.stopDozing();
Winson2799eca2016-02-25 12:10:42 -0800323 return;
Winson190fe3bf2015-10-20 14:57:24 -0700324 }
Winson2799eca2016-02-25 12:10:42 -0800325
326 // Defer to the activity to handle hiding recents, if it handles it, then it must still
327 // be visible
328 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
329 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700330 }
331
Winson190fe3bf2015-10-20 14:57:24 -0700332 public void toggleRecents() {
Winson6b92c6e2015-11-06 13:11:16 -0800333 // Skip this toggle if we are already waiting to trigger recents via alt-tab
334 if (mFastAltTabTrigger.isDozing()) {
335 return;
336 }
337
Jorim Jaggidd98d412015-11-18 15:57:38 -0800338 mDraggingInRecents = false;
Jorim Jaggie161f082016-02-05 14:26:16 -0800339 mLaunchedWhileDocking = false;
Winson190fe3bf2015-10-20 14:57:24 -0700340 mTriggeredFromAltTab = false;
341
342 try {
Winsone7f138c2015-10-22 16:15:21 -0700343 SystemServicesProxy ssp = Recents.getSystemServices();
344 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700345 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsonb61e6542016-02-04 14:37:18 -0800346 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
347
Winsone7f138c2015-10-22 16:15:21 -0700348 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Winsone693aaf2016-03-01 12:05:59 -0800349 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
Winson5da43472015-11-04 17:39:55 -0800350 RecentsConfiguration config = Recents.getConfiguration();
351 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson Chungead5c0f2015-12-14 11:18:57 -0500352 if (!launchState.launchedWithAltTab) {
Winsonb61e6542016-02-04 14:37:18 -0800353 // If the user taps quickly
Winsone693aaf2016-03-01 12:05:59 -0800354 if (!debugFlags.isPagingEnabled() ||
355 (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
356 elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
Winsonb61e6542016-02-04 14:37:18 -0800357 // 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 */,
Winsone693aaf2016-03-01 12:05:59 -0800577 true /* launchedWhileDockingTask*/);
Chong Zhange4fbd322016-03-01 14:44:03 -0800578 }
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);
Winson88737542016-02-17 13:27:33 -0800605 mDummyStackView = new TaskStackView(mContext);
Winsonb94443d2016-01-07 15:34:13 -0800606 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 */
Winson88737542016-02-17 13:27:33 -0800618 private void updateHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) {
Winson53ec42c2015-10-28 15:55:35 -0700619 RecentsConfiguration config = Recents.getConfiguration();
Winsone7f138c2015-10-22 16:15:21 -0700620 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800621 Rect systemInsets = new Rect();
622 ssp.getStableInsets(systemInsets);
Winsone7f138c2015-10-22 16:15:21 -0700623 Rect windowRect = ssp.getWindowRect();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800624 calculateWindowStableInsets(systemInsets, windowRect);
625 windowRect.offsetTo(0, 0);
Winson190fe3bf2015-10-20 14:57:24 -0700626
627 // Update the configuration for the current state
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800628 config.update(systemInsets);
Winson190fe3bf2015-10-20 14:57:24 -0700629
Winsond8b1d632016-01-04 17:51:18 -0800630 if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
Winson190fe3bf2015-10-20 14:57:24 -0700631 // Try and pre-emptively bind the search widget on startup to ensure that we
632 // have the right thumbnail bounds to animate to.
633 // Note: We have to reload the widget id before we get the task stack bounds below
Winsone7f138c2015-10-22 16:15:21 -0700634 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
Winson53ec42c2015-10-28 15:55:35 -0700635 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
Winson190fe3bf2015-10-20 14:57:24 -0700636 }
637 }
Winson53ec42c2015-10-28 15:55:35 -0700638 config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
Winson190fe3bf2015-10-20 14:57:24 -0700639 mSearchBarBounds, mTaskStackBounds);
640
641 // Rebind the header bar and draw it for the transition
Winson88737542016-02-17 13:27:33 -0800642 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
Winson190fe3bf2015-10-20 14:57:24 -0700643 Rect taskStackBounds = new Rect(mTaskStackBounds);
Winson88737542016-02-17 13:27:33 -0800644 stackLayout.setSystemInsets(systemInsets);
Winsonf0d1c442015-12-01 11:04:45 -0800645 if (stack != null) {
Winson88737542016-02-17 13:27:33 -0800646 stackLayout.initialize(taskStackBounds,
Winsonf0d1c442015-12-01 11:04:45 -0800647 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
Winson88737542016-02-17 13:27:33 -0800648 mDummyStackView.setTasks(stack, false /* notifyStackChanges */);
Winsonf0d1c442015-12-01 11:04:45 -0800649 }
Winson88737542016-02-17 13:27:33 -0800650 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
Winson190fe3bf2015-10-20 14:57:24 -0700651 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
Winson190fe3bf2015-10-20 14:57:24 -0700708 final Task toTask = new Task();
Winsone693aaf2016-03-01 12:05:59 -0800709 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
Winson3fb67562015-11-11 10:39:03 -0800710 ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
Winson190fe3bf2015-10-20 14:57:24 -0700711 @Override
Winsonab84fc52015-10-23 11:52:07 -0700712 public void run() {
713 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
714 mHandler.post(new Runnable() {
715 @Override
716 public void run() {
717 mThumbnailTransitionBitmapCache = transitionBitmap;
718 mThumbnailTransitionBitmapCacheKey = toTask;
719 }
720 });
Winson190fe3bf2015-10-20 14:57:24 -0700721 }
Winsonab84fc52015-10-23 11:52:07 -0700722 });
Winson190fe3bf2015-10-20 14:57:24 -0700723 }
724
725 /**
726 * Creates the activity options for a unknown state->recents transition.
727 */
728 private ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700729 return ActivityOptions.makeCustomAnimation(mContext,
730 R.anim.recents_from_unknown_enter,
731 R.anim.recents_from_unknown_exit,
Winson3fb67562015-11-11 10:39:03 -0800732 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700733 }
734
735 /**
736 * Creates the activity options for a home->recents transition.
737 */
738 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
Winson190fe3bf2015-10-20 14:57:24 -0700739 if (fromSearchHome) {
740 return ActivityOptions.makeCustomAnimation(mContext,
741 R.anim.recents_from_search_launcher_enter,
742 R.anim.recents_from_search_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800743 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700744 }
745 return ActivityOptions.makeCustomAnimation(mContext,
746 R.anim.recents_from_launcher_enter,
747 R.anim.recents_from_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800748 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700749 }
750
751 /**
752 * Creates the activity options for an app->recents transition.
753 */
754 private ActivityOptions getThumbnailTransitionActivityOptions(
Winsone693aaf2016-03-01 12:05:59 -0800755 ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700756 if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
757 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
Winsone693aaf2016-03-01 12:05:59 -0800758 ArrayList<Task> tasks = stackView.getStack().getStackTasks();
759 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
760 TaskStackViewScroller stackScroller = stackView.getScroller();
761
762 stackView.updateToInitialState();
763
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700764 for (int i = tasks.size() - 1; i >= 0; i--) {
765 Task task = tasks.get(i);
Winson387aac62015-11-25 11:18:56 -0800766 if (task.isFreeformTask()) {
Winsone693aaf2016-03-01 12:05:59 -0800767 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
768 stackScroller.getStackScroll(), mTmpTransform, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700769 Rect toTaskRect = new Rect();
770 mTmpTransform.rect.round(toTaskRect);
771 Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
772 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
773 }
774 }
775 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
776 specs.toArray(specsArray);
777 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3fb67562015-11-11 10:39:03 -0800778 specsArray, mHandler, null, this);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700779 } else {
780 // Update the destination rect
781 Task toTask = new Task();
Winsone693aaf2016-03-01 12:05:59 -0800782 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, 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 */
Winsone693aaf2016-03-01 12:05:59 -0800814 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
815 Task runningTaskOut) {
Winson190fe3bf2015-10-20 14:57:24 -0700816 // Find the running task in the TaskStack
Winsone693aaf2016-03-01 12:05:59 -0800817 TaskStack stack = stackView.getStack();
Winson65c851e2016-01-20 12:43:35 -0800818 Task launchTask = stack.getLaunchTarget();
819 if (launchTask != null) {
820 runningTaskOut.copyFrom(launchTask);
821 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700822 // If no task is specified or we can not find the task just use the front most one
Winson35a8b042016-01-22 09:41:09 -0800823 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
Winson65c851e2016-01-20 12:43:35 -0800824 runningTaskOut.copyFrom(launchTask);
Winson190fe3bf2015-10-20 14:57:24 -0700825 }
826
827 // Get the transform for the running task
Winsone693aaf2016-03-01 12:05:59 -0800828 stackView.updateToInitialState();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800829 mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
Winson190fe3bf2015-10-20 14:57:24 -0700830 stackView.getScroller().getStackScroll(), mTmpTransform, null);
831 return mTmpTransform;
832 }
833
834 /**
835 * Draws the header of a task used for the window animation into a bitmap.
836 */
837 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
Winson8be16342016-02-09 11:53:27 -0800838 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700839 if (toTransform != null && toTask.key != null) {
840 Bitmap thumbnail;
841 synchronized (mHeaderBarLock) {
Winson Chung509d0d02015-12-16 15:43:12 -0500842 int toHeaderWidth = (int) toTransform.rect.width();
Winson190fe3bf2015-10-20 14:57:24 -0700843 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
Winson8be16342016-02-09 11:53:27 -0800844 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
Winson Chung509d0d02015-12-16 15:43:12 -0500845 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
846 (int) toTransform.rect.height());
Winson190fe3bf2015-10-20 14:57:24 -0700847 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
848 Bitmap.Config.ARGB_8888);
Winsonc742f972015-11-12 11:32:21 -0800849 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson190fe3bf2015-10-20 14:57:24 -0700850 thumbnail.eraseColor(0xFFff0000);
851 } else {
852 Canvas c = new Canvas(thumbnail);
853 c.scale(toTransform.scale, toTransform.scale);
Winson8be16342016-02-09 11:53:27 -0800854 mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
855 disabledInSafeMode);
Winsone693aaf2016-03-01 12:05:59 -0800856 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
Winson190fe3bf2015-10-20 14:57:24 -0700857 mHeaderBar.draw(c);
858 c.setBitmap(null);
859 }
860 }
861 return thumbnail.createAshmemBitmap();
862 }
863 return null;
864 }
865
866 /**
867 * Shows the recents activity
868 */
869 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
Jorim Jaggibb42a462015-11-20 16:27:16 -0800870 boolean isTopTaskHome, boolean animate) {
Winsone7f138c2015-10-22 16:15:21 -0700871 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700872
Winsone5f1faa2015-11-20 12:26:23 -0800873 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800874 // should always preload the tasks now. If we are dragging in recents, reload them as
875 // the stacks might have changed.
Jorim Jaggie161f082016-02-05 14:26:16 -0800876 if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
Winsone5f1faa2015-11-20 12:26:23 -0800877 // Create a new load plan if preloadRecents() was never triggered
Winson190fe3bf2015-10-20 14:57:24 -0700878 sInstanceLoadPlan = loader.createLoadPlan(mContext);
879 }
Jorim Jaggie161f082016-02-05 14:26:16 -0800880 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
Winson65c851e2016-01-20 12:43:35 -0800881 loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
Winson190fe3bf2015-10-20 14:57:24 -0700882 }
883 TaskStack stack = sInstanceLoadPlan.getTaskStack();
884
Winsonf0d1c442015-12-01 11:04:45 -0800885 // Update the header bar if necessary
Winsonb94443d2016-01-07 15:34:13 -0800886 updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
Winsonf0d1c442015-12-01 11:04:45 -0800887
Winson190fe3bf2015-10-20 14:57:24 -0700888 // Prepare the dummy stack for the transition
Winson36a5a2c2015-10-29 18:04:39 -0700889 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
Winson190fe3bf2015-10-20 14:57:24 -0700890 mDummyStackView.computeStackVisibilityReport();
Jorim Jaggibb42a462015-11-20 16:27:16 -0800891
892 if (!animate) {
893 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
894 startRecentsActivity(topTask, opts, false /* fromHome */,
895 false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
896 return;
897 }
898
Winson4b057c62016-01-12 15:01:52 -0800899 boolean hasRecentTasks = stack.getTaskCount() > 0;
Winson190fe3bf2015-10-20 14:57:24 -0700900 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
901
902 if (useThumbnailTransition) {
903 // Try starting with a thumbnail transition
Winsone693aaf2016-03-01 12:05:59 -0800904 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
Winson190fe3bf2015-10-20 14:57:24 -0700905 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,
Winsone693aaf2016-03-01 12:05:59 -0800951 ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
952 boolean fromThumbnail, 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;
Winsone693aaf2016-03-01 12:05:59 -0800958 launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
959 launchState.launchedFromAppDocked = mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700960 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
961 launchState.launchedWithAltTab = mTriggeredFromAltTab;
962 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
963 launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
964 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
965 launchState.launchedHasConfigurationChanged = false;
Winsonb1e71d02015-11-23 12:40:23 -0800966 launchState.launchedViaDragGesture = mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800967 launchState.launchedWhileDocking = mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700968
969 Intent intent = new Intent();
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800970 intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
Winson190fe3bf2015-10-20 14:57:24 -0700971 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
972 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
973 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800974
Winson190fe3bf2015-10-20 14:57:24 -0700975 if (opts != null) {
976 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
977 } else {
978 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
979 }
980 mCanReuseTaskStackViews = true;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800981 EventBus.getDefault().send(new RecentsActivityStartingEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700982 }
983
Winson3fb67562015-11-11 10:39:03 -0800984 /**** OnAnimationFinishedListener Implementation ****/
Filip Gruszczynski1a5203d2015-10-29 17:43:49 -0700985
986 @Override
987 public void onAnimationFinished() {
988 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
989 }
Winson190fe3bf2015-10-20 14:57:24 -0700990}