blob: c05f71ac2d7291a4d6d0892977fad9818f79617d [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;
Winson94bc4f22016-04-07 14:22:12 -070020import static android.view.View.MeasureSpec;
Winsone693aaf2016-03-01 12:05:59 -080021
Winson190fe3bf2015-10-20 14:57:24 -070022import android.app.ActivityManager;
23import android.app.ActivityOptions;
Winson190fe3bf2015-10-20 14:57:24 -070024import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070025import android.content.Context;
26import android.content.Intent;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Rect;
Winson3150e572015-10-23 15:07:24 -070031import android.graphics.RectF;
Winson22574af2016-03-23 19:00:28 -070032import android.graphics.drawable.Drawable;
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;
Winsonb61e6542016-02-04 14:37:18 -080040import android.view.ViewConfiguration;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070041import android.view.WindowManager;
Winsonc0d70582016-01-29 10:24:39 -080042
Winson675c5d82016-08-23 17:12:22 -070043import android.widget.Toast;
Winson190fe3bf2015-10-20 14:57:24 -070044import com.android.internal.logging.MetricsLogger;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070045import com.android.internal.policy.DockedDividerUtils;
Winson190fe3bf2015-10-20 14:57:24 -070046import com.android.systemui.R;
47import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070048import com.android.systemui.recents.events.EventBus;
Jorim Jaggi899327f2016-02-25 20:44:18 -050049import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
Winson1b585612015-11-06 09:16:26 -080050import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
Winson412e1802015-10-20 16:57:57 -070051import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070052import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Manu Corneta96a6172017-01-19 12:40:44 -080053import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
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;
Jaewan Kim938a50b2016-03-14 17:35:43 +090064import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
Winson190fe3bf2015-10-20 14:57:24 -070065import com.android.systemui.recents.model.RecentsTaskLoadPlan;
66import com.android.systemui.recents.model.RecentsTaskLoader;
67import com.android.systemui.recents.model.Task;
68import com.android.systemui.recents.model.TaskGrouping;
69import com.android.systemui.recents.model.TaskStack;
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;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070075import com.android.systemui.stackdivider.DividerView;
Winsond8b1d632016-01-04 17:51:18 -080076import com.android.systemui.statusbar.BaseStatusBar;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080077import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
Winson190fe3bf2015-10-20 14:57:24 -070078import com.android.systemui.statusbar.phone.PhoneStatusBar;
79
80import java.util.ArrayList;
81
82/**
83 * An implementation of the Recents component for the current user. For secondary users, this can
84 * be called remotely from the system user.
85 */
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080086public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
Winson190fe3bf2015-10-20 14:57:24 -070087
88 private final static String TAG = "RecentsImpl";
Winsonb61e6542016-02-04 14:37:18 -080089
Winson6b92c6e2015-11-06 13:11:16 -080090 // The minimum amount of time between each recents button press that we will handle
91 private final static int MIN_TOGGLE_DELAY_MS = 350;
Winsonb61e6542016-02-04 14:37:18 -080092
Winson6b92c6e2015-11-06 13:11:16 -080093 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
94 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
95 // duration, then we will toggle recents after this duration.
96 private final static int FAST_ALT_TAB_DELAY_MS = 225;
Winson190fe3bf2015-10-20 14:57:24 -070097
98 public final static String RECENTS_PACKAGE = "com.android.systemui";
99 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
Winsone693aaf2016-03-01 12:05:59 -0800100
Winson190fe3bf2015-10-20 14:57:24 -0700101 /**
Jaewan Kim938a50b2016-03-14 17:35:43 +0900102 * An implementation of TaskStackListener, that allows us to listen for changes to the system
Winson190fe3bf2015-10-20 14:57:24 -0700103 * task stacks and update recents accordingly.
104 */
Jaewan Kim938a50b2016-03-14 17:35:43 +0900105 class TaskStackListenerImpl extends TaskStackListener {
Winson190fe3bf2015-10-20 14:57:24 -0700106 @Override
107 public void onTaskStackChanged() {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900108 // Preloads the next task
Jorim Jaggia0fdeec2016-01-07 14:42:28 +0100109 RecentsConfiguration config = Recents.getConfiguration();
Winson190fe3bf2015-10-20 14:57:24 -0700110 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winsone7f138c2015-10-22 16:15:21 -0700111 RecentsTaskLoader loader = Recents.getTaskLoader();
112 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond46b7272016-04-20 11:54:27 -0700113 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
Winson190fe3bf2015-10-20 14:57:24 -0700114
115 // Load the next task only if we aren't svelte
116 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700117 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson190fe3bf2015-10-20 14:57:24 -0700118 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
119 // This callback is made when a new activity is launched and the old one is paused
120 // so ignore the current activity and try and preload the thumbnail for the
121 // previous one.
122 if (runningTaskInfo != null) {
123 launchOpts.runningTaskId = runningTaskInfo.id;
124 }
125 launchOpts.numVisibleTasks = 2;
126 launchOpts.numVisibleTaskThumbnails = 2;
127 launchOpts.onlyLoadForCache = true;
128 launchOpts.onlyLoadPausedActivities = true;
129 loader.loadTasks(mContext, plan, launchOpts);
130 }
Winson190fe3bf2015-10-20 14:57:24 -0700131 }
132 }
133
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700134 protected static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700135
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700136 protected Context mContext;
137 protected Handler mHandler;
Winson190fe3bf2015-10-20 14:57:24 -0700138 TaskStackListenerImpl mTaskStackListener;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800139 boolean mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800140 boolean mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700141
142 // Task launching
Winson190fe3bf2015-10-20 14:57:24 -0700143 Rect mTaskStackBounds = new Rect();
Winson190fe3bf2015-10-20 14:57:24 -0700144 TaskViewTransform mTmpTransform = new TaskViewTransform();
145 int mStatusBarHeight;
146 int mNavBarHeight;
147 int mNavBarWidth;
148 int mTaskBarHeight;
149
150 // Header (for transition)
151 TaskViewHeader mHeaderBar;
152 final Object mHeaderBarLock = new Object();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700153 protected TaskStackView mDummyStackView;
Winson190fe3bf2015-10-20 14:57:24 -0700154
155 // Variables to keep track of if we need to start recents after binding
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700156 protected boolean mTriggeredFromAltTab;
157 protected long mLastToggleTime;
Winson6b92c6e2015-11-06 13:11:16 -0800158 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
159 @Override
160 public void run() {
161 // When this fires, then the user has not released alt-tab for at least
162 // FAST_ALT_TAB_DELAY_MS milliseconds
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800163 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700164 false /* reloadTasks */, false /* fromHome */,
165 DividerView.INVALID_RECENTS_GROW_TARGET);
Winson6b92c6e2015-11-06 13:11:16 -0800166 }
167 });
Winson190fe3bf2015-10-20 14:57:24 -0700168
Winsonaeb298c2016-04-05 13:08:11 -0700169 protected Bitmap mThumbTransitionBitmapCache;
Winson190fe3bf2015-10-20 14:57:24 -0700170
Winson190fe3bf2015-10-20 14:57:24 -0700171 public RecentsImpl(Context context) {
172 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700173 mHandler = new Handler();
Winson190fe3bf2015-10-20 14:57:24 -0700174
Winsonab84fc52015-10-23 11:52:07 -0700175 // Initialize the static foreground thread
176 ForegroundThread.get();
177
Winson190fe3bf2015-10-20 14:57:24 -0700178 // Register the task stack listener
Jaewan Kim938a50b2016-03-14 17:35:43 +0900179 mTaskStackListener = new TaskStackListenerImpl();
Winsone7f138c2015-10-22 16:15:21 -0700180 SystemServicesProxy ssp = Recents.getSystemServices();
181 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700182
183 // Initialize the static configuration resources
Winson670ea712016-04-12 17:02:26 -0700184 mDummyStackView = new TaskStackView(mContext);
Winson670ea712016-04-12 17:02:26 -0700185 reloadResources();
Winson16ef39a2016-06-28 18:25:15 -0700186 }
Winson190fe3bf2015-10-20 14:57:24 -0700187
Winson Chung501d59d2016-10-05 17:49:09 +0000188 public void onBootCompleted() {
Winson190fe3bf2015-10-20 14:57:24 -0700189 // When we start, preload the data associated with the previous recent tasks.
190 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700191 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700192 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700193 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson190fe3bf2015-10-20 14:57:24 -0700194 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winson Chung296278a2015-12-17 12:09:02 -0500195 launchOpts.numVisibleTasks = loader.getIconCacheSize();
Winson190fe3bf2015-10-20 14:57:24 -0700196 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
197 launchOpts.onlyLoadForCache = true;
198 loader.loadTasks(mContext, plan, launchOpts);
199 }
200
Winson190fe3bf2015-10-20 14:57:24 -0700201 public void onConfigurationChanged() {
Winson96f78f52016-09-21 12:14:16 -0700202 Resources res = mContext.getResources();
Winson670ea712016-04-12 17:02:26 -0700203 reloadResources();
204 mDummyStackView.reloadOnConfigurationChange();
Winson190fe3bf2015-10-20 14:57:24 -0700205 }
206
207 /**
208 * This is only called from the system user's Recents. Secondary users will instead proxy their
209 * visibility change events through to the system user via
210 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
211 */
212 public void onVisibilityChanged(Context context, boolean visible) {
Winsonab216602016-08-09 14:05:20 -0700213 Recents.getSystemServices().setRecentsVisibility(visible);
Winson190fe3bf2015-10-20 14:57:24 -0700214 }
215
216 /**
217 * This is only called from the system user's Recents. Secondary users will instead proxy their
218 * visibility change events through to the system user via
219 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
220 */
Andrii Kulian0f051f52016-04-14 00:41:51 -0700221 public void onStartScreenPinning(Context context, int taskId) {
Winson190fe3bf2015-10-20 14:57:24 -0700222 SystemUIApplication app = (SystemUIApplication) context;
223 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
224 if (statusBar != null) {
Andrii Kulian0f051f52016-04-14 00:41:51 -0700225 statusBar.showScreenPinningRequest(taskId, false);
Winson190fe3bf2015-10-20 14:57:24 -0700226 }
227 }
228
Jorim Jaggibb42a462015-11-20 16:27:16 -0800229 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700230 boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
231 int growTarget) {
Winson190fe3bf2015-10-20 14:57:24 -0700232 mTriggeredFromAltTab = triggeredFromAltTab;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800233 mDraggingInRecents = draggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800234 mLaunchedWhileDocking = launchedWhileDockingTask;
Winsone693aaf2016-03-01 12:05:59 -0800235 if (mFastAltTabTrigger.isAsleep()) {
236 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
237 mFastAltTabTrigger.stopDozing();
Winson6b92c6e2015-11-06 13:11:16 -0800238 } else if (mFastAltTabTrigger.isDozing()) {
Winsone693aaf2016-03-01 12:05:59 -0800239 // Fast alt-tab duration has not elapsed. If this is triggered by a different
240 // showRecents() call, then ignore that call for now.
241 // TODO: We can not handle quick tabs that happen between the initial showRecents() call
242 // that started the activity and the activity starting up. The severity of this
243 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
Winson6b92c6e2015-11-06 13:11:16 -0800244 if (!triggeredFromAltTab) {
245 return;
246 }
247 mFastAltTabTrigger.stopDozing();
Winsone693aaf2016-03-01 12:05:59 -0800248 } else if (triggeredFromAltTab) {
249 // The fast alt-tab detector is not yet running, so start the trigger and wait for the
250 // hideRecents() call, or for the fast alt-tab duration to elapse
251 mFastAltTabTrigger.startDozing();
252 return;
Winson6b92c6e2015-11-06 13:11:16 -0800253 }
Winson190fe3bf2015-10-20 14:57:24 -0700254
255 try {
256 // Check if the top task is in the home stack, and start the recents activity
Winsone7f138c2015-10-22 16:15:21 -0700257 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond7cb5172016-05-04 16:14:19 -0700258 boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
259 MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
260 if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
Winsond46b7272016-04-20 11:54:27 -0700261 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
262 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
263 growTarget);
Winson190fe3bf2015-10-20 14:57:24 -0700264 }
265 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800266 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700267 }
268 }
269
Winson190fe3bf2015-10-20 14:57:24 -0700270 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
Winson2799eca2016-02-25 12:10:42 -0800271 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
272 // The user has released alt-tab before the trigger has run, so just show the next
273 // task immediately
274 showNextTask();
Winson6b92c6e2015-11-06 13:11:16 -0800275
Winson2799eca2016-02-25 12:10:42 -0800276 // Cancel the fast alt-tab trigger
277 mFastAltTabTrigger.stopDozing();
Winson2799eca2016-02-25 12:10:42 -0800278 return;
Winson190fe3bf2015-10-20 14:57:24 -0700279 }
Winson2799eca2016-02-25 12:10:42 -0800280
281 // Defer to the activity to handle hiding recents, if it handles it, then it must still
282 // be visible
283 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
284 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700285 }
286
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700287 public void toggleRecents(int growTarget) {
Winson6b92c6e2015-11-06 13:11:16 -0800288 // Skip this toggle if we are already waiting to trigger recents via alt-tab
289 if (mFastAltTabTrigger.isDozing()) {
290 return;
291 }
292
Jorim Jaggidd98d412015-11-18 15:57:38 -0800293 mDraggingInRecents = false;
Jorim Jaggie161f082016-02-05 14:26:16 -0800294 mLaunchedWhileDocking = false;
Winson190fe3bf2015-10-20 14:57:24 -0700295 mTriggeredFromAltTab = false;
296
297 try {
Winsone7f138c2015-10-22 16:15:21 -0700298 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond46b7272016-04-20 11:54:27 -0700299 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
Winsonb61e6542016-02-04 14:37:18 -0800300 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
301
Winsond46b7272016-04-20 11:54:27 -0700302 if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
Winsone693aaf2016-03-01 12:05:59 -0800303 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
Winson5da43472015-11-04 17:39:55 -0800304 RecentsConfiguration config = Recents.getConfiguration();
305 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson Chungead5c0f2015-12-14 11:18:57 -0500306 if (!launchState.launchedWithAltTab) {
Manu Corneta96a6172017-01-19 12:40:44 -0800307 // Has the user tapped quickly?
308 boolean isQuickTap = ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
309 elapsedTime < ViewConfiguration.getDoubleTapTimeout();
310 if (Recents.getConfiguration().isGridEnabled) {
311 if (isQuickTap) {
312 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
313 } else {
314 EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
315 }
Winsonb61e6542016-02-04 14:37:18 -0800316 } else {
Manu Corneta96a6172017-01-19 12:40:44 -0800317 if (!debugFlags.isPagingEnabled() || isQuickTap) {
318 // Launch the next focused task
319 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
320 } else {
321 // Notify recents to move onto the next task
322 EventBus.getDefault().post(new IterateRecentsEvent());
323 }
Winsonb61e6542016-02-04 14:37:18 -0800324 }
Winson0d14d4d2015-10-26 17:05:04 -0700325 } else {
326 // If the user has toggled it too quickly, then just eat up the event here (it's
327 // better than showing a janky screenshot).
328 // NOTE: Ideally, the screenshot mechanism would take the window transform into
329 // account
Winsonb61e6542016-02-04 14:37:18 -0800330 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700331 return;
332 }
333
334 EventBus.getDefault().post(new ToggleRecentsEvent());
335 mLastToggleTime = SystemClock.elapsedRealtime();
336 }
Winson190fe3bf2015-10-20 14:57:24 -0700337 return;
338 } else {
Winson0d14d4d2015-10-26 17:05:04 -0700339 // If the user has toggled it too quickly, then just eat up the event here (it's
340 // better than showing a janky screenshot).
341 // NOTE: Ideally, the screenshot mechanism would take the window transform into
342 // account
Winsonb61e6542016-02-04 14:37:18 -0800343 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700344 return;
345 }
346
Winson190fe3bf2015-10-20 14:57:24 -0700347 // Otherwise, start the recents activity
Winsond46b7272016-04-20 11:54:27 -0700348 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
349 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
350 growTarget);
Winsond8b1d632016-01-04 17:51:18 -0800351
352 // Only close the other system windows if we are actually showing recents
353 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
Winson0d14d4d2015-10-26 17:05:04 -0700354 mLastToggleTime = SystemClock.elapsedRealtime();
Winson190fe3bf2015-10-20 14:57:24 -0700355 }
356 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800357 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700358 }
359 }
360
Winson190fe3bf2015-10-20 14:57:24 -0700361 public void preloadRecents() {
362 // Preload only the raw task list into a new load plan (which will be consumed by the
363 // RecentsActivity) only if there is a task to animate to.
Winsone7f138c2015-10-22 16:15:21 -0700364 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond46b7272016-04-20 11:54:27 -0700365 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
366 if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
367 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson5b4e0d22016-02-16 18:11:35 -0800368 RecentsTaskLoader loader = Recents.getTaskLoader();
369 sInstanceLoadPlan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700370 sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
371 loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
Winson190fe3bf2015-10-20 14:57:24 -0700372 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winson4b057c62016-01-12 15:01:52 -0800373 if (stack.getTaskCount() > 0) {
Winsonaeb298c2016-04-05 13:08:11 -0700374 // Only preload the icon (but not the thumbnail since it may not have been taken for
375 // the pausing activity)
Winsonc4a038a2016-05-26 16:42:15 -0700376 preloadIcon(runningTask.id);
Winsonaeb298c2016-04-05 13:08:11 -0700377
378 // At this point, we don't know anything about the stack state. So only calculate
379 // the dimensions of the thumbnail that we need for the transition into Recents, but
380 // do not draw it until we construct the activity options when we start Recents
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700381 updateHeaderBarLayout(stack, null /* window rect override*/);
Winson190fe3bf2015-10-20 14:57:24 -0700382 }
383 }
384 }
385
Winson190fe3bf2015-10-20 14:57:24 -0700386 public void cancelPreloadingRecents() {
387 // Do nothing
388 }
389
Jorim Jaggidd98d412015-11-18 15:57:38 -0800390 public void onDraggingInRecents(float distanceFromTop) {
391 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
392 }
393
Jorim Jaggidd98d412015-11-18 15:57:38 -0800394 public void onDraggingInRecentsEnded(float velocity) {
395 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
396 }
397
Winson675c5d82016-08-23 17:12:22 -0700398 public void onShowCurrentUserToast(int msgResId, int msgLength) {
399 Toast.makeText(mContext, msgResId, msgLength).show();
400 }
401
Winson6b92c6e2015-11-06 13:11:16 -0800402 /**
403 * Transitions to the next recent task in the stack.
404 */
405 public void showNextTask() {
Winsone7f138c2015-10-22 16:15:21 -0700406 SystemServicesProxy ssp = Recents.getSystemServices();
Winson6b92c6e2015-11-06 13:11:16 -0800407 RecentsTaskLoader loader = Recents.getTaskLoader();
408 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700409 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson6b92c6e2015-11-06 13:11:16 -0800410 TaskStack focusedStack = plan.getTaskStack();
411
412 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800413 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson6b92c6e2015-11-06 13:11:16 -0800414
Winson6b92c6e2015-11-06 13:11:16 -0800415 // Return early if there is no running task
Winsond46b7272016-04-20 11:54:27 -0700416 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson6b92c6e2015-11-06 13:11:16 -0800417 if (runningTask == null) return;
Winson6b92c6e2015-11-06 13:11:16 -0800418
419 // Find the task in the recents list
Winsond46b7272016-04-20 11:54:27 -0700420 boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
Winson250608a2015-11-24 15:00:31 -0800421 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson6b92c6e2015-11-06 13:11:16 -0800422 Task toTask = null;
423 ActivityOptions launchOpts = null;
424 int taskCount = tasks.size();
425 for (int i = taskCount - 1; i >= 1; i--) {
426 Task task = tasks.get(i);
Winsond46b7272016-04-20 11:54:27 -0700427 if (isRunningTaskInHomeStack) {
Winsone86deb82015-11-12 09:32:10 -0800428 toTask = tasks.get(i - 1);
429 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
430 R.anim.recents_launch_next_affiliated_task_target,
431 R.anim.recents_fast_toggle_app_home_exit);
432 break;
433 } else if (task.key.id == runningTask.id) {
Winson6b92c6e2015-11-06 13:11:16 -0800434 toTask = tasks.get(i - 1);
435 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
436 R.anim.recents_launch_prev_affiliated_task_target,
437 R.anim.recents_launch_prev_affiliated_task_source);
438 break;
439 }
440 }
441
442 // Return early if there is no next task
443 if (toTask == null) {
444 ssp.startInPlaceAnimationOnFrontMostApplication(
445 ActivityOptions.makeCustomInPlaceAnimation(mContext,
446 R.anim.recents_launch_prev_affiliated_task_bounce));
447 return;
448 }
449
450 // Launch the task
Wale Ogunwale64ae08a2016-03-28 08:15:27 -0700451 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
Winson6b92c6e2015-11-06 13:11:16 -0800452 }
453
454 /**
455 * Transitions to the next affiliated task.
456 */
457 public void showRelativeAffiliatedTask(boolean showNextTask) {
458 SystemServicesProxy ssp = Recents.getSystemServices();
Winsone7f138c2015-10-22 16:15:21 -0700459 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700460 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700461 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson190fe3bf2015-10-20 14:57:24 -0700462 TaskStack focusedStack = plan.getTaskStack();
463
464 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800465 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson190fe3bf2015-10-20 14:57:24 -0700466
Winson190fe3bf2015-10-20 14:57:24 -0700467 // Return early if there is no running task (can't determine affiliated tasks in this case)
Winsond46b7272016-04-20 11:54:27 -0700468 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson190fe3bf2015-10-20 14:57:24 -0700469 if (runningTask == null) return;
470 // Return early if the running task is in the home stack (optimization)
Winson5510f6c2015-10-27 12:11:26 -0700471 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
Winson190fe3bf2015-10-20 14:57:24 -0700472
473 // Find the task in the recents list
Winson250608a2015-11-24 15:00:31 -0800474 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson190fe3bf2015-10-20 14:57:24 -0700475 Task toTask = null;
476 ActivityOptions launchOpts = null;
477 int taskCount = tasks.size();
478 int numAffiliatedTasks = 0;
479 for (int i = 0; i < taskCount; i++) {
480 Task task = tasks.get(i);
481 if (task.key.id == runningTask.id) {
482 TaskGrouping group = task.group;
483 Task.TaskKey toTaskKey;
484 if (showNextTask) {
485 toTaskKey = group.getNextTaskInGroup(task);
486 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
487 R.anim.recents_launch_next_affiliated_task_target,
488 R.anim.recents_launch_next_affiliated_task_source);
489 } else {
490 toTaskKey = group.getPrevTaskInGroup(task);
491 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
492 R.anim.recents_launch_prev_affiliated_task_target,
493 R.anim.recents_launch_prev_affiliated_task_source);
494 }
495 if (toTaskKey != null) {
496 toTask = focusedStack.findTaskWithId(toTaskKey.id);
497 }
498 numAffiliatedTasks = group.getTaskCount();
499 break;
500 }
501 }
502
503 // Return early if there is no next task
504 if (toTask == null) {
505 if (numAffiliatedTasks > 1) {
506 if (showNextTask) {
Winsone7f138c2015-10-22 16:15:21 -0700507 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700508 ActivityOptions.makeCustomInPlaceAnimation(mContext,
509 R.anim.recents_launch_next_affiliated_task_bounce));
510 } else {
Winsone7f138c2015-10-22 16:15:21 -0700511 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700512 ActivityOptions.makeCustomInPlaceAnimation(mContext,
513 R.anim.recents_launch_prev_affiliated_task_bounce));
514 }
515 }
516 return;
517 }
518
519 // Keep track of actually launched affiliated tasks
520 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
521
522 // Launch the task
Wale Ogunwale64ae08a2016-03-28 08:15:27 -0700523 ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700524 }
525
526 public void showNextAffiliatedTask() {
527 // Keep track of when the affiliated task is triggered
528 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
529 showRelativeAffiliatedTask(true);
530 }
531
532 public void showPrevAffiliatedTask() {
533 // Keep track of when the affiliated task is triggered
534 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
535 showRelativeAffiliatedTask(false);
536 }
537
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800538 public void dockTopTask(int topTaskId, int dragMode,
539 int stackCreateMode, Rect initialBounds) {
Jorim Jaggi75b25972015-10-21 14:51:10 +0200540 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggi9511b0f2016-01-29 19:12:44 -0800541
542 // Make sure we inform DividerView before we actually start the activity so we can change
543 // the resize mode already.
Chong Zhange4fbd322016-03-01 14:44:03 -0800544 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
Jorim Jaggi899327f2016-02-25 20:44:18 -0500545 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
Chong Zhange4fbd322016-03-01 14:44:03 -0800546 showRecents(
547 false /* triggeredFromAltTab */,
548 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
549 false /* animate */,
Jorim Jaggi681fc7b2016-04-14 22:02:39 -0700550 true /* launchedWhileDockingTask*/,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700551 false /* fromHome */,
552 DividerView.INVALID_RECENTS_GROW_TARGET);
Chong Zhange4fbd322016-03-01 14:44:03 -0800553 }
Jorim Jaggi75b25972015-10-21 14:51:10 +0200554 }
555
Winson190fe3bf2015-10-20 14:57:24 -0700556 /**
557 * Returns the preloaded load plan and invalidates it.
558 */
559 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
560 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
561 sInstanceLoadPlan = null;
562 return plan;
563 }
564
565 /**
Winson670ea712016-04-12 17:02:26 -0700566 * Reloads all the resources for the current configuration.
Winsonb94443d2016-01-07 15:34:13 -0800567 */
Winson670ea712016-04-12 17:02:26 -0700568 private void reloadResources() {
Winsonb94443d2016-01-07 15:34:13 -0800569 Resources res = mContext.getResources();
Winsonb94443d2016-01-07 15:34:13 -0800570
571 mStatusBarHeight = res.getDimensionPixelSize(
572 com.android.internal.R.dimen.status_bar_height);
573 mNavBarHeight = res.getDimensionPixelSize(
574 com.android.internal.R.dimen.navigation_bar_height);
575 mNavBarWidth = res.getDimensionPixelSize(
576 com.android.internal.R.dimen.navigation_bar_width);
Jorim Jaggi25160db2016-04-18 16:03:36 -0700577 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
Winson21700932016-03-24 17:26:23 -0700578 R.dimen.recents_task_view_header_height,
579 R.dimen.recents_task_view_header_height,
580 R.dimen.recents_task_view_header_height,
581 R.dimen.recents_task_view_header_height_tablet_land,
582 R.dimen.recents_task_view_header_height,
Manu Cornetbf8e2902016-12-20 08:29:33 -0800583 R.dimen.recents_task_view_header_height_tablet_land,
584 R.dimen.recents_grid_task_view_header_height);
585
586 LayoutInflater inflater = LayoutInflater.from(mContext);
587 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
588 null, false);
589 mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
Winsonb94443d2016-01-07 15:34:13 -0800590 }
591
592 /**
Winson190fe3bf2015-10-20 14:57:24 -0700593 * Prepares the header bar layout for the next transition, if the task view bounds has changed
594 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
595 *
Winsonf0d1c442015-12-01 11:04:45 -0800596 * @param stack the stack to initialize the stack layout with
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700597 * @param windowRectOverride the rectangle to use when calculating the stack state which can
598 * be different from the current window rect if recents is resizing
599 * while being launched
Winson190fe3bf2015-10-20 14:57:24 -0700600 */
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700601 private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
Winsone7f138c2015-10-22 16:15:21 -0700602 SystemServicesProxy ssp = Recents.getSystemServices();
Winsonfc48b072016-04-21 11:20:11 -0700603 Rect displayRect = ssp.getDisplayRect();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800604 Rect systemInsets = new Rect();
605 ssp.getStableInsets(systemInsets);
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700606 Rect windowRect = windowRectOverride != null
607 ? new Rect(windowRectOverride)
608 : ssp.getWindowRect();
Winsoncf9b8322016-03-31 15:36:07 -0700609 // When docked, the nav bar insets are consumed and the activity is measured without insets.
610 // However, the window bounds include the insets, so we need to subtract them here to make
611 // them identical.
612 if (ssp.hasDockedTask()) {
613 windowRect.bottom -= systemInsets.bottom;
614 systemInsets.bottom = 0;
615 }
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800616 calculateWindowStableInsets(systemInsets, windowRect);
617 windowRect.offsetTo(0, 0);
Winson190fe3bf2015-10-20 14:57:24 -0700618
Winson59924fe2016-03-17 14:13:18 -0700619 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
Winson190fe3bf2015-10-20 14:57:24 -0700620
621 // Rebind the header bar and draw it for the transition
Winson88737542016-02-17 13:27:33 -0800622 stackLayout.setSystemInsets(systemInsets);
Winsonf0d1c442015-12-01 11:04:45 -0800623 if (stack != null) {
Winsonfc48b072016-04-21 11:20:11 -0700624 stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
Winsoncbb625b2016-07-06 15:24:15 -0700625 systemInsets.left, systemInsets.right, mTaskStackBounds);
Winsonaeb298c2016-04-05 13:08:11 -0700626 stackLayout.reset();
Winsonfc48b072016-04-21 11:20:11 -0700627 stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
Winsonf0d1c442015-12-01 11:04:45 -0800628 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
Winsona1ededd2016-03-25 12:23:12 -0700629 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
Winson190fe3bf2015-10-20 14:57:24 -0700630
Winsonaeb298c2016-04-05 13:08:11 -0700631 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
Winson94bc4f22016-04-07 14:22:12 -0700632 if (!taskViewBounds.isEmpty()) {
633 int taskViewWidth = taskViewBounds.width();
634 synchronized (mHeaderBarLock) {
635 if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
636 mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
Winson96f78f52016-09-21 12:14:16 -0700637 mHeaderBar.forceLayout();
Winson94bc4f22016-04-07 14:22:12 -0700638 mHeaderBar.measure(
639 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
640 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
641 }
642 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
Winsonaeb298c2016-04-05 13:08:11 -0700643 }
Winsonaeb298c2016-04-05 13:08:11 -0700644
Winson94bc4f22016-04-07 14:22:12 -0700645 // Update the transition bitmap to match the new header bar height
646 if (mThumbTransitionBitmapCache == null ||
647 (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
648 (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
649 mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
650 mTaskBarHeight, Bitmap.Config.ARGB_8888);
651 }
Winsonaeb298c2016-04-05 13:08:11 -0700652 }
Winson190fe3bf2015-10-20 14:57:24 -0700653 }
654 }
655
656 /**
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800657 * Given the stable insets and the rect for our window, calculates the insets that affect our
658 * window.
659 */
660 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
661 Rect displayRect = Recents.getSystemServices().getDisplayRect();
662
663 // Display rect without insets - available app space
664 Rect appRect = new Rect(displayRect);
665 appRect.inset(inOutInsets);
666
667 // Our window intersected with available app space
668 Rect windowRectWithInsets = new Rect(windowRect);
669 windowRectWithInsets.intersect(appRect);
670 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
671 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
672 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
673 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
674 }
675
676 /**
Winson190fe3bf2015-10-20 14:57:24 -0700677 * Preloads the icon of a task.
678 */
Winsonc4a038a2016-05-26 16:42:15 -0700679 private void preloadIcon(int runningTaskId) {
Winson190fe3bf2015-10-20 14:57:24 -0700680 // Ensure that we load the running task's icon
681 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winsonc4a038a2016-05-26 16:42:15 -0700682 launchOpts.runningTaskId = runningTaskId;
Winson190fe3bf2015-10-20 14:57:24 -0700683 launchOpts.loadThumbnails = false;
684 launchOpts.onlyLoadForCache = true;
Winsone7f138c2015-10-22 16:15:21 -0700685 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700686 }
687
688 /**
Winson190fe3bf2015-10-20 14:57:24 -0700689 * Creates the activity options for a unknown state->recents transition.
690 */
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700691 protected ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700692 return ActivityOptions.makeCustomAnimation(mContext,
693 R.anim.recents_from_unknown_enter,
694 R.anim.recents_from_unknown_exit,
Winson3fb67562015-11-11 10:39:03 -0800695 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700696 }
697
698 /**
699 * Creates the activity options for a home->recents transition.
700 */
Winson008ee15f2016-03-18 17:17:25 -0700701 protected ActivityOptions getHomeTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700702 return ActivityOptions.makeCustomAnimation(mContext,
703 R.anim.recents_from_launcher_enter,
704 R.anim.recents_from_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800705 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700706 }
707
708 /**
709 * Creates the activity options for an app->recents transition.
710 */
711 private ActivityOptions getThumbnailTransitionActivityOptions(
Winsond46b7272016-04-20 11:54:27 -0700712 ActivityManager.RunningTaskInfo runningTask, TaskStackView stackView,
713 Rect windowOverrideRect) {
Winsonc4a038a2016-05-26 16:42:15 -0700714 if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700715 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
Winsone693aaf2016-03-01 12:05:59 -0800716 ArrayList<Task> tasks = stackView.getStack().getStackTasks();
717 TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
718 TaskStackViewScroller stackScroller = stackView.getScroller();
719
Winson003eda62016-03-11 14:56:00 -0800720 stackView.updateLayoutAlgorithm(true /* boundScroll */);
Winson67c79572016-04-13 14:02:18 -0700721 stackView.updateToInitialState();
Winsone693aaf2016-03-01 12:05:59 -0800722
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700723 for (int i = tasks.size() - 1; i >= 0; i--) {
724 Task task = tasks.get(i);
Winson387aac62015-11-25 11:18:56 -0800725 if (task.isFreeformTask()) {
Winsone693aaf2016-03-01 12:05:59 -0800726 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700727 stackScroller.getStackScroll(), mTmpTransform, null,
Jiaquan He26f637b2016-12-27 14:44:14 -0800728 windowOverrideRect);
Winsonaeb298c2016-04-05 13:08:11 -0700729 Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
730 mThumbTransitionBitmapCache);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700731 Rect toTaskRect = new Rect();
732 mTmpTransform.rect.round(toTaskRect);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700733 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
734 }
735 }
736 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
737 specs.toArray(specsArray);
738 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3fb67562015-11-11 10:39:03 -0800739 specsArray, mHandler, null, this);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700740 } else {
741 // Update the destination rect
742 Task toTask = new Task();
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700743 TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
744 windowOverrideRect);
Manu Cornetfc6ac0a2017-01-18 15:42:14 -0800745 // When using a grid layout, the header is already visible on screen at the target
746 // location, making it unnecessary to draw it in the transition thumbnail.
747 Bitmap thumbnail = stackView.useGridLayout()
748 ? mThumbTransitionBitmapCache.createAshmemBitmap()
749 : drawThumbnailTransitionBitmap(toTask, toTransform,
750 mThumbTransitionBitmapCache);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700751 if (thumbnail != null) {
Winsonaeb298c2016-04-05 13:08:11 -0700752 RectF toTaskRect = toTransform.rect;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700753 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
754 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
Winson3fb67562015-11-11 10:39:03 -0800755 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700756 }
757 // If both the screenshot and thumbnail fails, then just fall back to the default transition
758 return getUnknownTransitionActivityOptions();
759 }
760 }
Winson190fe3bf2015-10-20 14:57:24 -0700761
Winson190fe3bf2015-10-20 14:57:24 -0700762 /**
763 * Returns the transition rect for the given task id.
764 */
Winsone693aaf2016-03-01 12:05:59 -0800765 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700766 Task runningTaskOut, Rect windowOverrideRect) {
Winson190fe3bf2015-10-20 14:57:24 -0700767 // Find the running task in the TaskStack
Winsone693aaf2016-03-01 12:05:59 -0800768 TaskStack stack = stackView.getStack();
Winson65c851e2016-01-20 12:43:35 -0800769 Task launchTask = stack.getLaunchTarget();
770 if (launchTask != null) {
771 runningTaskOut.copyFrom(launchTask);
772 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700773 // If no task is specified or we can not find the task just use the front most one
Winson35a8b042016-01-22 09:41:09 -0800774 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
Winson65c851e2016-01-20 12:43:35 -0800775 runningTaskOut.copyFrom(launchTask);
Winson190fe3bf2015-10-20 14:57:24 -0700776 }
777
778 // Get the transform for the running task
Winson003eda62016-03-11 14:56:00 -0800779 stackView.updateLayoutAlgorithm(true /* boundScroll */);
Winson67c79572016-04-13 14:02:18 -0700780 stackView.updateToInitialState();
Winson7845e8c2016-03-29 10:23:19 -0700781 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
Jiaquan He26f637b2016-12-27 14:44:14 -0800782 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
Winson190fe3bf2015-10-20 14:57:24 -0700783 return mTmpTransform;
784 }
785
786 /**
787 * Draws the header of a task used for the window animation into a bitmap.
788 */
Winsonaeb298c2016-04-05 13:08:11 -0700789 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
790 Bitmap thumbnail) {
Winson8be16342016-02-09 11:53:27 -0800791 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700792 if (toTransform != null && toTask.key != null) {
Winson190fe3bf2015-10-20 14:57:24 -0700793 synchronized (mHeaderBarLock) {
Winson8be16342016-02-09 11:53:27 -0800794 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
Winson Chung509d0d02015-12-16 15:43:12 -0500795 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
796 (int) toTransform.rect.height());
Winsonc742f972015-11-12 11:32:21 -0800797 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson190fe3bf2015-10-20 14:57:24 -0700798 thumbnail.eraseColor(0xFFff0000);
799 } else {
Winsonaeb298c2016-04-05 13:08:11 -0700800 thumbnail.eraseColor(0);
Winson190fe3bf2015-10-20 14:57:24 -0700801 Canvas c = new Canvas(thumbnail);
Winson22574af2016-03-23 19:00:28 -0700802 // Workaround for b/27815919, reset the callback so that we do not trigger an
803 // invalidate on the header bar as a result of updating the icon
804 Drawable icon = mHeaderBar.getIconView().getDrawable();
805 if (icon != null) {
806 icon.setCallback(null);
807 }
Winsond2a03062016-04-15 11:19:07 -0700808 mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
Winson8be16342016-02-09 11:53:27 -0800809 disabledInSafeMode);
Winsond2a03062016-04-15 11:19:07 -0700810 mHeaderBar.onTaskDataLoaded();
Winsone693aaf2016-03-01 12:05:59 -0800811 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
Winson190fe3bf2015-10-20 14:57:24 -0700812 mHeaderBar.draw(c);
813 c.setBitmap(null);
814 }
815 }
816 return thumbnail.createAshmemBitmap();
817 }
818 return null;
819 }
820
821 /**
822 * Shows the recents activity
823 */
Winsond46b7272016-04-20 11:54:27 -0700824 protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
825 boolean isHomeStackVisible, boolean animate, int growTarget) {
Winsone7f138c2015-10-22 16:15:21 -0700826 RecentsTaskLoader loader = Recents.getTaskLoader();
Winsonc5b12dd2016-03-23 20:25:12 -0700827 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
Winsonfc73eec2016-08-01 15:48:34 -0700828 SystemServicesProxy ssp = Recents.getSystemServices();
829 boolean isBlacklisted = (runningTask != null)
830 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
831 : false;
Winson190fe3bf2015-10-20 14:57:24 -0700832
Winsonfc73eec2016-08-01 15:48:34 -0700833 int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
Winsonc4a038a2016-05-26 16:42:15 -0700834 ? runningTask.id
835 : -1;
836
Winsone5f1faa2015-11-20 12:26:23 -0800837 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800838 // should always preload the tasks now. If we are dragging in recents, reload them as
839 // the stacks might have changed.
Winsonfc73eec2016-08-01 15:48:34 -0700840 if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
Winsone5f1faa2015-11-20 12:26:23 -0800841 // Create a new load plan if preloadRecents() was never triggered
Winson190fe3bf2015-10-20 14:57:24 -0700842 sInstanceLoadPlan = loader.createLoadPlan(mContext);
843 }
Jorim Jaggie161f082016-02-05 14:26:16 -0800844 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
Winsonc4a038a2016-05-26 16:42:15 -0700845 loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
Winson190fe3bf2015-10-20 14:57:24 -0700846 }
Winsonc5b12dd2016-03-23 20:25:12 -0700847
Winson190fe3bf2015-10-20 14:57:24 -0700848 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winsonc5b12dd2016-03-23 20:25:12 -0700849 boolean hasRecentTasks = stack.getTaskCount() > 0;
Winsonfc73eec2016-08-01 15:48:34 -0700850 boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
851 hasRecentTasks;
Winson190fe3bf2015-10-20 14:57:24 -0700852
Winsonaeb298c2016-04-05 13:08:11 -0700853 // Update the launch state that we need in updateHeaderBarLayout()
Jorim Jaggic5887ea2016-05-13 18:21:48 -0700854 launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
Winsonaeb298c2016-04-05 13:08:11 -0700855 launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
Winsonfc73eec2016-08-01 15:48:34 -0700856 launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
Winsonaeb298c2016-04-05 13:08:11 -0700857 launchState.launchedViaDockGesture = mLaunchedWhileDocking;
858 launchState.launchedViaDragGesture = mDraggingInRecents;
Winsonc4a038a2016-05-26 16:42:15 -0700859 launchState.launchedToTaskId = runningTaskId;
Winsonaeb298c2016-04-05 13:08:11 -0700860 launchState.launchedWithAltTab = mTriggeredFromAltTab;
861
862 // Preload the icon (this will be a null-op if we have preloaded the icon already in
863 // preloadRecents())
Winsonc4a038a2016-05-26 16:42:15 -0700864 preloadIcon(runningTaskId);
Winsonaeb298c2016-04-05 13:08:11 -0700865
Winsonf0d1c442015-12-01 11:04:45 -0800866 // Update the header bar if necessary
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700867 Rect windowOverrideRect = getWindowRectOverride(growTarget);
868 updateHeaderBarLayout(stack, windowOverrideRect);
Winsonf0d1c442015-12-01 11:04:45 -0800869
Winson190fe3bf2015-10-20 14:57:24 -0700870 // Prepare the dummy stack for the transition
Winson36a5a2c2015-10-29 18:04:39 -0700871 TaskStackLayoutAlgorithm.VisibilityReport stackVr =
Winson190fe3bf2015-10-20 14:57:24 -0700872 mDummyStackView.computeStackVisibilityReport();
Jorim Jaggibb42a462015-11-20 16:27:16 -0800873
Winsonaeb298c2016-04-05 13:08:11 -0700874 // Update the remaining launch state
Winsonc5b12dd2016-03-23 20:25:12 -0700875 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
876 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
Winsonc5b12dd2016-03-23 20:25:12 -0700877
Jorim Jaggibb42a462015-11-20 16:27:16 -0800878 if (!animate) {
Winsonc5b12dd2016-03-23 20:25:12 -0700879 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
Jorim Jaggibb42a462015-11-20 16:27:16 -0800880 return;
881 }
882
Winsonaeb298c2016-04-05 13:08:11 -0700883 ActivityOptions opts;
Winsonfc73eec2016-08-01 15:48:34 -0700884 if (isBlacklisted) {
885 opts = getUnknownTransitionActivityOptions();
886 } else if (useThumbnailTransition) {
Winson190fe3bf2015-10-20 14:57:24 -0700887 // Try starting with a thumbnail transition
Winsond46b7272016-04-20 11:54:27 -0700888 opts = getThumbnailTransitionActivityOptions(runningTask, mDummyStackView,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700889 windowOverrideRect);
Winsonaeb298c2016-04-05 13:08:11 -0700890 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700891 // If there is no thumbnail transition, but is launching from home into recents, then
Winson008ee15f2016-03-18 17:17:25 -0700892 // use a quick home transition
Winsonaeb298c2016-04-05 13:08:11 -0700893 opts = hasRecentTasks
894 ? getHomeTransitionActivityOptions()
895 : getUnknownTransitionActivityOptions();
Winson190fe3bf2015-10-20 14:57:24 -0700896 }
Winsonaeb298c2016-04-05 13:08:11 -0700897 startRecentsActivity(opts);
Winson190fe3bf2015-10-20 14:57:24 -0700898 mLastToggleTime = SystemClock.elapsedRealtime();
899 }
900
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700901 private Rect getWindowRectOverride(int growTarget) {
902 if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
903 return null;
904 }
905 Rect result = new Rect();
906 Rect displayRect = Recents.getSystemServices().getDisplayRect();
907 DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
908 result, displayRect.width(), displayRect.height(),
909 Recents.getSystemServices().getDockedDividerSize(mContext));
910 return result;
911 }
912
Winson190fe3bf2015-10-20 14:57:24 -0700913 /**
914 * Starts the recents activity.
915 */
Winsonc5b12dd2016-03-23 20:25:12 -0700916 private void startRecentsActivity(ActivityOptions opts) {
Winson190fe3bf2015-10-20 14:57:24 -0700917 Intent intent = new Intent();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700918 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
Winson190fe3bf2015-10-20 14:57:24 -0700919 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
920 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
921 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800922
Winson190fe3bf2015-10-20 14:57:24 -0700923 if (opts != null) {
924 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
925 } else {
926 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
927 }
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800928 EventBus.getDefault().send(new RecentsActivityStartingEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700929 }
930
Winson3fb67562015-11-11 10:39:03 -0800931 /**** OnAnimationFinishedListener Implementation ****/
Filip Gruszczynski1a5203d2015-10-29 17:43:49 -0700932
933 @Override
934 public void onAnimationFinished() {
935 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
936 }
Winson190fe3bf2015-10-20 14:57:24 -0700937}