blob: 2522d0f9107accb8c7db265f18292b9663757627 [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;
Vladislav Kaznacheev1f6b7092016-05-04 17:18:56 -070020import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
Matthew Ngae1ff4f2016-11-10 15:49:14 -080021import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
Winson94bc4f22016-04-07 14:22:12 -070022import static android.view.View.MeasureSpec;
Winsone693aaf2016-03-01 12:05:59 -080023
Winson190fe3bf2015-10-20 14:57:24 -070024import android.app.ActivityManager;
Jorim Jaggifb9d78a2017-01-05 18:57:12 +010025import android.app.ActivityManager.TaskSnapshot;
Winson190fe3bf2015-10-20 14:57:24 -070026import android.app.ActivityOptions;
Winson190fe3bf2015-10-20 14:57:24 -070027import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070028import android.content.Context;
29import android.content.Intent;
30import 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;
Winson22574af2016-03-23 19:00:28 -070035import android.graphics.drawable.Drawable;
Winson190fe3bf2015-10-20 14:57:24 -070036import android.os.Handler;
37import android.os.SystemClock;
38import android.os.UserHandle;
Winson1b585612015-11-06 09:16:26 -080039import android.util.Log;
Winson190fe3bf2015-10-20 14:57:24 -070040import android.util.MutableBoolean;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -070041import android.view.AppTransitionAnimationSpec;
Winson190fe3bf2015-10-20 14:57:24 -070042import android.view.LayoutInflater;
Winsonb61e6542016-02-04 14:37:18 -080043import android.view.ViewConfiguration;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070044import android.view.WindowManager;
Winsonc0d70582016-01-29 10:24:39 -080045
Winson675c5d82016-08-23 17:12:22 -070046import android.widget.Toast;
Winson190fe3bf2015-10-20 14:57:24 -070047import com.android.internal.logging.MetricsLogger;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070048import com.android.internal.policy.DockedDividerUtils;
Winson190fe3bf2015-10-20 14:57:24 -070049import com.android.systemui.R;
50import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070051import com.android.systemui.recents.events.EventBus;
Jorim Jaggi899327f2016-02-25 20:44:18 -050052import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
Winson1b585612015-11-06 09:16:26 -080053import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
Winson412e1802015-10-20 16:57:57 -070054import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070055import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Manu Corneta96a6172017-01-19 12:40:44 -080056import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
Winsonb61e6542016-02-04 14:37:18 -080057import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080058import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
Winson412e1802015-10-20 16:57:57 -070059import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
Winson190fe3bf2015-10-20 14:57:24 -070060import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
61import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
Jorim Jaggidd98d412015-11-18 15:57:38 -080062import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
63import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
Jorim Jaggifb9d78a2017-01-05 18:57:12 +010064import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
Winson6b92c6e2015-11-06 13:11:16 -080065import com.android.systemui.recents.misc.DozeTrigger;
Winsonab84fc52015-10-23 11:52:07 -070066import com.android.systemui.recents.misc.ForegroundThread;
Winson190fe3bf2015-10-20 14:57:24 -070067import com.android.systemui.recents.misc.SystemServicesProxy;
Jaewan Kim938a50b2016-03-14 17:35:43 +090068import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
Winson190fe3bf2015-10-20 14:57:24 -070069import com.android.systemui.recents.model.RecentsTaskLoadPlan;
70import com.android.systemui.recents.model.RecentsTaskLoader;
71import com.android.systemui.recents.model.Task;
72import com.android.systemui.recents.model.TaskGrouping;
73import com.android.systemui.recents.model.TaskStack;
Winson36a5a2c2015-10-29 18:04:39 -070074import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
Jorim Jaggicdef5912017-04-03 17:24:19 +020075import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
Winson1b585612015-11-06 09:16:26 -080076import com.android.systemui.recents.views.TaskStackView;
Winsone693aaf2016-03-01 12:05:59 -080077import com.android.systemui.recents.views.TaskStackViewScroller;
Winson190fe3bf2015-10-20 14:57:24 -070078import com.android.systemui.recents.views.TaskViewHeader;
79import com.android.systemui.recents.views.TaskViewTransform;
Manu Cornetde3cfb02017-01-25 18:31:01 +090080import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
Jorim Jaggidb21bbd2016-04-18 15:32:07 -070081import com.android.systemui.stackdivider.DividerView;
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080082import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
Jason Monk2a6ea9c2017-01-26 11:14:51 -050083import com.android.systemui.statusbar.phone.StatusBar;
Winson190fe3bf2015-10-20 14:57:24 -070084
85import java.util.ArrayList;
86
87/**
88 * An implementation of the Recents component for the current user. For secondary users, this can
89 * be called remotely from the system user.
90 */
Jorim Jaggicdb06ca2016-01-25 19:15:12 -080091public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
Winson190fe3bf2015-10-20 14:57:24 -070092
93 private final static String TAG = "RecentsImpl";
Winsonb61e6542016-02-04 14:37:18 -080094
Winson6b92c6e2015-11-06 13:11:16 -080095 // The minimum amount of time between each recents button press that we will handle
96 private final static int MIN_TOGGLE_DELAY_MS = 350;
Winsonb61e6542016-02-04 14:37:18 -080097
Winson6b92c6e2015-11-06 13:11:16 -080098 // The duration within which the user releasing the alt tab (from when they pressed alt tab)
99 // that the fast alt-tab animation will run. If the user's alt-tab takes longer than this
100 // duration, then we will toggle recents after this duration.
101 private final static int FAST_ALT_TAB_DELAY_MS = 225;
Winson190fe3bf2015-10-20 14:57:24 -0700102
103 public final static String RECENTS_PACKAGE = "com.android.systemui";
104 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
Winsone693aaf2016-03-01 12:05:59 -0800105
Winson190fe3bf2015-10-20 14:57:24 -0700106 /**
Jaewan Kim938a50b2016-03-14 17:35:43 +0900107 * An implementation of TaskStackListener, that allows us to listen for changes to the system
Winson190fe3bf2015-10-20 14:57:24 -0700108 * task stacks and update recents accordingly.
109 */
Jaewan Kim938a50b2016-03-14 17:35:43 +0900110 class TaskStackListenerImpl extends TaskStackListener {
Winson Chungf3cfa892017-04-24 12:23:47 -0700111
Winson190fe3bf2015-10-20 14:57:24 -0700112 @Override
Winson Chungf3cfa892017-04-24 12:23:47 -0700113 public void onTaskStackChangedBackground() {
Jaewan Kim938a50b2016-03-14 17:35:43 +0900114 // Preloads the next task
Jorim Jaggia0fdeec2016-01-07 14:42:28 +0100115 RecentsConfiguration config = Recents.getConfiguration();
Winson190fe3bf2015-10-20 14:57:24 -0700116 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winson190fe3bf2015-10-20 14:57:24 -0700117
118 // Load the next task only if we aren't svelte
Winson Chungf3cfa892017-04-24 12:23:47 -0700119 SystemServicesProxy ssp = Recents.getSystemServices();
120 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
121 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700122 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700123 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson Chungf3cfa892017-04-24 12:23:47 -0700124
Winson190fe3bf2015-10-20 14:57:24 -0700125 // This callback is made when a new activity is launched and the old one is paused
126 // so ignore the current activity and try and preload the thumbnail for the
127 // previous one.
Winson Chungf3cfa892017-04-24 12:23:47 -0700128 VisibilityReport visibilityReport;
129 synchronized (mDummyStackView) {
130 mDummyStackView.setTasks(plan.getTaskStack(), false /* allowNotify */);
131 updateDummyStackViewLayout(plan.getTaskStack(),
132 getWindowRect(null /* windowRectOverride */));
133
134 // Launched from app is always the worst case (in terms of how many
135 // thumbnails/tasks visible)
136 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
137 launchState.launchedFromApp = true;
138 mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */, launchState);
139 visibilityReport = mDummyStackView.computeStackVisibilityReport();
Winson190fe3bf2015-10-20 14:57:24 -0700140 }
Jorim Jaggicdef5912017-04-03 17:24:19 +0200141
Winson Chungf3cfa892017-04-24 12:23:47 -0700142 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
143 launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
Jorim Jaggicdef5912017-04-03 17:24:19 +0200144 launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
145 launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
Winson190fe3bf2015-10-20 14:57:24 -0700146 launchOpts.onlyLoadForCache = true;
147 launchOpts.onlyLoadPausedActivities = true;
Jorim Jaggicdef5912017-04-03 17:24:19 +0200148 launchOpts.loadThumbnails = true;
Winson190fe3bf2015-10-20 14:57:24 -0700149 loader.loadTasks(mContext, plan, launchOpts);
150 }
Winson190fe3bf2015-10-20 14:57:24 -0700151 }
Jorim Jaggifb9d78a2017-01-05 18:57:12 +0100152
153 @Override
154 public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
155 EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
156 }
Winson190fe3bf2015-10-20 14:57:24 -0700157 }
158
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700159 protected static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700160
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700161 protected Context mContext;
162 protected Handler mHandler;
Winson190fe3bf2015-10-20 14:57:24 -0700163 TaskStackListenerImpl mTaskStackListener;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800164 boolean mDraggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800165 boolean mLaunchedWhileDocking;
Winson190fe3bf2015-10-20 14:57:24 -0700166
167 // Task launching
Winson190fe3bf2015-10-20 14:57:24 -0700168 Rect mTaskStackBounds = new Rect();
Winson190fe3bf2015-10-20 14:57:24 -0700169 TaskViewTransform mTmpTransform = new TaskViewTransform();
170 int mStatusBarHeight;
171 int mNavBarHeight;
172 int mNavBarWidth;
173 int mTaskBarHeight;
174
175 // Header (for transition)
176 TaskViewHeader mHeaderBar;
177 final Object mHeaderBarLock = new Object();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700178 protected TaskStackView mDummyStackView;
Winson190fe3bf2015-10-20 14:57:24 -0700179
180 // Variables to keep track of if we need to start recents after binding
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700181 protected boolean mTriggeredFromAltTab;
182 protected long mLastToggleTime;
Winson6b92c6e2015-11-06 13:11:16 -0800183 DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
184 @Override
185 public void run() {
186 // When this fires, then the user has not released alt-tab for at least
187 // FAST_ALT_TAB_DELAY_MS milliseconds
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800188 showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700189 false /* reloadTasks */, false /* fromHome */,
190 DividerView.INVALID_RECENTS_GROW_TARGET);
Winson6b92c6e2015-11-06 13:11:16 -0800191 }
192 });
Winson190fe3bf2015-10-20 14:57:24 -0700193
Winsonaeb298c2016-04-05 13:08:11 -0700194 protected Bitmap mThumbTransitionBitmapCache;
Winson190fe3bf2015-10-20 14:57:24 -0700195
Winson190fe3bf2015-10-20 14:57:24 -0700196 public RecentsImpl(Context context) {
197 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700198 mHandler = new Handler();
Winson190fe3bf2015-10-20 14:57:24 -0700199
Winsonab84fc52015-10-23 11:52:07 -0700200 // Initialize the static foreground thread
201 ForegroundThread.get();
202
Winson190fe3bf2015-10-20 14:57:24 -0700203 // Register the task stack listener
Jaewan Kim938a50b2016-03-14 17:35:43 +0900204 mTaskStackListener = new TaskStackListenerImpl();
Winsone7f138c2015-10-22 16:15:21 -0700205 SystemServicesProxy ssp = Recents.getSystemServices();
206 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700207
208 // Initialize the static configuration resources
Winson670ea712016-04-12 17:02:26 -0700209 mDummyStackView = new TaskStackView(mContext);
Winson670ea712016-04-12 17:02:26 -0700210 reloadResources();
Winson16ef39a2016-06-28 18:25:15 -0700211 }
Winson190fe3bf2015-10-20 14:57:24 -0700212
Winson Chung501d59d2016-10-05 17:49:09 +0000213 public void onBootCompleted() {
Winson190fe3bf2015-10-20 14:57:24 -0700214 // When we start, preload the data associated with the previous recent tasks.
215 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700216 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700217 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700218 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson190fe3bf2015-10-20 14:57:24 -0700219 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winson Chung296278a2015-12-17 12:09:02 -0500220 launchOpts.numVisibleTasks = loader.getIconCacheSize();
Winson190fe3bf2015-10-20 14:57:24 -0700221 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
222 launchOpts.onlyLoadForCache = true;
223 loader.loadTasks(mContext, plan, launchOpts);
224 }
225
Winson190fe3bf2015-10-20 14:57:24 -0700226 public void onConfigurationChanged() {
Winson670ea712016-04-12 17:02:26 -0700227 reloadResources();
Winson Chungf3cfa892017-04-24 12:23:47 -0700228 synchronized (mDummyStackView) {
229 mDummyStackView.reloadOnConfigurationChange();
230 }
Winson190fe3bf2015-10-20 14:57:24 -0700231 }
232
233 /**
234 * This is only called from the system user's Recents. Secondary users will instead proxy their
235 * visibility change events through to the system user via
236 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
237 */
238 public void onVisibilityChanged(Context context, boolean visible) {
Winsonab216602016-08-09 14:05:20 -0700239 Recents.getSystemServices().setRecentsVisibility(visible);
Winson190fe3bf2015-10-20 14:57:24 -0700240 }
241
242 /**
243 * This is only called from the system user's Recents. Secondary users will instead proxy their
244 * visibility change events through to the system user via
245 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
246 */
Andrii Kulian0f051f52016-04-14 00:41:51 -0700247 public void onStartScreenPinning(Context context, int taskId) {
Winson190fe3bf2015-10-20 14:57:24 -0700248 SystemUIApplication app = (SystemUIApplication) context;
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500249 StatusBar statusBar = app.getComponent(StatusBar.class);
Winson190fe3bf2015-10-20 14:57:24 -0700250 if (statusBar != null) {
Andrii Kulian0f051f52016-04-14 00:41:51 -0700251 statusBar.showScreenPinningRequest(taskId, false);
Winson190fe3bf2015-10-20 14:57:24 -0700252 }
253 }
254
Jorim Jaggibb42a462015-11-20 16:27:16 -0800255 public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700256 boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
257 int growTarget) {
Winson190fe3bf2015-10-20 14:57:24 -0700258 mTriggeredFromAltTab = triggeredFromAltTab;
Jorim Jaggidd98d412015-11-18 15:57:38 -0800259 mDraggingInRecents = draggingInRecents;
Jorim Jaggie161f082016-02-05 14:26:16 -0800260 mLaunchedWhileDocking = launchedWhileDockingTask;
Winsone693aaf2016-03-01 12:05:59 -0800261 if (mFastAltTabTrigger.isAsleep()) {
262 // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
263 mFastAltTabTrigger.stopDozing();
Winson6b92c6e2015-11-06 13:11:16 -0800264 } else if (mFastAltTabTrigger.isDozing()) {
Winsone693aaf2016-03-01 12:05:59 -0800265 // Fast alt-tab duration has not elapsed. If this is triggered by a different
266 // showRecents() call, then ignore that call for now.
267 // TODO: We can not handle quick tabs that happen between the initial showRecents() call
268 // that started the activity and the activity starting up. The severity of this
269 // is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
Winson6b92c6e2015-11-06 13:11:16 -0800270 if (!triggeredFromAltTab) {
271 return;
272 }
273 mFastAltTabTrigger.stopDozing();
Winsone693aaf2016-03-01 12:05:59 -0800274 } else if (triggeredFromAltTab) {
275 // The fast alt-tab detector is not yet running, so start the trigger and wait for the
276 // hideRecents() call, or for the fast alt-tab duration to elapse
277 mFastAltTabTrigger.startDozing();
278 return;
Winson6b92c6e2015-11-06 13:11:16 -0800279 }
Winson190fe3bf2015-10-20 14:57:24 -0700280
281 try {
282 // Check if the top task is in the home stack, and start the recents activity
Winsone7f138c2015-10-22 16:15:21 -0700283 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond7cb5172016-05-04 16:14:19 -0700284 boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
285 MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
286 if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
Winsond46b7272016-04-20 11:54:27 -0700287 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
288 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
289 growTarget);
Winson190fe3bf2015-10-20 14:57:24 -0700290 }
291 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800292 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700293 }
294 }
295
Winson190fe3bf2015-10-20 14:57:24 -0700296 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
Winson2799eca2016-02-25 12:10:42 -0800297 if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
298 // The user has released alt-tab before the trigger has run, so just show the next
299 // task immediately
300 showNextTask();
Winson6b92c6e2015-11-06 13:11:16 -0800301
Winson2799eca2016-02-25 12:10:42 -0800302 // Cancel the fast alt-tab trigger
303 mFastAltTabTrigger.stopDozing();
Winson2799eca2016-02-25 12:10:42 -0800304 return;
Winson190fe3bf2015-10-20 14:57:24 -0700305 }
Winson2799eca2016-02-25 12:10:42 -0800306
307 // Defer to the activity to handle hiding recents, if it handles it, then it must still
308 // be visible
309 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
310 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700311 }
312
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700313 public void toggleRecents(int growTarget) {
Winson6b92c6e2015-11-06 13:11:16 -0800314 // Skip this toggle if we are already waiting to trigger recents via alt-tab
315 if (mFastAltTabTrigger.isDozing()) {
316 return;
317 }
318
Jorim Jaggidd98d412015-11-18 15:57:38 -0800319 mDraggingInRecents = false;
Jorim Jaggie161f082016-02-05 14:26:16 -0800320 mLaunchedWhileDocking = false;
Winson190fe3bf2015-10-20 14:57:24 -0700321 mTriggeredFromAltTab = false;
322
323 try {
Winsone7f138c2015-10-22 16:15:21 -0700324 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond46b7272016-04-20 11:54:27 -0700325 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
Winsonb61e6542016-02-04 14:37:18 -0800326 long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
327
Winsond46b7272016-04-20 11:54:27 -0700328 if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
Winsone693aaf2016-03-01 12:05:59 -0800329 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
Winson5da43472015-11-04 17:39:55 -0800330 RecentsConfiguration config = Recents.getConfiguration();
331 RecentsActivityLaunchState launchState = config.getLaunchState();
Winson Chungead5c0f2015-12-14 11:18:57 -0500332 if (!launchState.launchedWithAltTab) {
Manu Corneta96a6172017-01-19 12:40:44 -0800333 // Has the user tapped quickly?
Matthew Ngda3dd7c2017-04-28 17:33:22 -0700334 boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
Manu Corneta96a6172017-01-19 12:40:44 -0800335 if (Recents.getConfiguration().isGridEnabled) {
336 if (isQuickTap) {
337 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
338 } else {
339 EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
340 }
Winsonb61e6542016-02-04 14:37:18 -0800341 } else {
Manu Corneta96a6172017-01-19 12:40:44 -0800342 if (!debugFlags.isPagingEnabled() || isQuickTap) {
343 // Launch the next focused task
344 EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
345 } else {
346 // Notify recents to move onto the next task
347 EventBus.getDefault().post(new IterateRecentsEvent());
348 }
Winsonb61e6542016-02-04 14:37:18 -0800349 }
Winson0d14d4d2015-10-26 17:05:04 -0700350 } else {
351 // If the user has toggled it too quickly, then just eat up the event here (it's
352 // better than showing a janky screenshot).
353 // NOTE: Ideally, the screenshot mechanism would take the window transform into
354 // account
Winsonb61e6542016-02-04 14:37:18 -0800355 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
Winson0d14d4d2015-10-26 17:05:04 -0700356 return;
357 }
358
359 EventBus.getDefault().post(new ToggleRecentsEvent());
360 mLastToggleTime = SystemClock.elapsedRealtime();
361 }
Winson190fe3bf2015-10-20 14:57:24 -0700362 return;
363 } else {
Winson0d14d4d2015-10-26 17:05:04 -0700364 // 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
Winson190fe3bf2015-10-20 14:57:24 -0700372 // Otherwise, start the recents activity
Winsond46b7272016-04-20 11:54:27 -0700373 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
374 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
375 growTarget);
Winsond8b1d632016-01-04 17:51:18 -0800376
377 // Only close the other system windows if we are actually showing recents
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500378 ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
Winson0d14d4d2015-10-26 17:05:04 -0700379 mLastToggleTime = SystemClock.elapsedRealtime();
Winson190fe3bf2015-10-20 14:57:24 -0700380 }
381 } catch (ActivityNotFoundException e) {
Winson1b585612015-11-06 09:16:26 -0800382 Log.e(TAG, "Failed to launch RecentsActivity", e);
Winson190fe3bf2015-10-20 14:57:24 -0700383 }
384 }
385
Winson190fe3bf2015-10-20 14:57:24 -0700386 public void preloadRecents() {
387 // Preload only the raw task list into a new load plan (which will be consumed by the
388 // RecentsActivity) only if there is a task to animate to.
Winsone7f138c2015-10-22 16:15:21 -0700389 SystemServicesProxy ssp = Recents.getSystemServices();
Winsond46b7272016-04-20 11:54:27 -0700390 MutableBoolean isHomeStackVisible = new MutableBoolean(true);
391 if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
392 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson Chung96b7a512017-02-08 12:20:00 -0800393 if (runningTask == null) {
394 return;
395 }
396
Winson5b4e0d22016-02-16 18:11:35 -0800397 RecentsTaskLoader loader = Recents.getTaskLoader();
398 sInstanceLoadPlan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700399 loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
Winson190fe3bf2015-10-20 14:57:24 -0700400 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winson4b057c62016-01-12 15:01:52 -0800401 if (stack.getTaskCount() > 0) {
Winsonaeb298c2016-04-05 13:08:11 -0700402 // Only preload the icon (but not the thumbnail since it may not have been taken for
403 // the pausing activity)
Winsonc4a038a2016-05-26 16:42:15 -0700404 preloadIcon(runningTask.id);
Winsonaeb298c2016-04-05 13:08:11 -0700405
406 // At this point, we don't know anything about the stack state. So only calculate
407 // the dimensions of the thumbnail that we need for the transition into Recents, but
408 // do not draw it until we construct the activity options when we start Recents
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700409 updateHeaderBarLayout(stack, null /* window rect override*/);
Winson190fe3bf2015-10-20 14:57:24 -0700410 }
411 }
412 }
413
Winson190fe3bf2015-10-20 14:57:24 -0700414 public void cancelPreloadingRecents() {
415 // Do nothing
416 }
417
Jorim Jaggidd98d412015-11-18 15:57:38 -0800418 public void onDraggingInRecents(float distanceFromTop) {
419 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
420 }
421
Jorim Jaggidd98d412015-11-18 15:57:38 -0800422 public void onDraggingInRecentsEnded(float velocity) {
423 EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
424 }
425
Winson675c5d82016-08-23 17:12:22 -0700426 public void onShowCurrentUserToast(int msgResId, int msgLength) {
427 Toast.makeText(mContext, msgResId, msgLength).show();
428 }
429
Winson6b92c6e2015-11-06 13:11:16 -0800430 /**
431 * Transitions to the next recent task in the stack.
432 */
433 public void showNextTask() {
Winsone7f138c2015-10-22 16:15:21 -0700434 SystemServicesProxy ssp = Recents.getSystemServices();
Winson6b92c6e2015-11-06 13:11:16 -0800435 RecentsTaskLoader loader = Recents.getTaskLoader();
436 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700437 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson6b92c6e2015-11-06 13:11:16 -0800438 TaskStack focusedStack = plan.getTaskStack();
439
440 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800441 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson6b92c6e2015-11-06 13:11:16 -0800442
Winson6b92c6e2015-11-06 13:11:16 -0800443 // Return early if there is no running task
Winsond46b7272016-04-20 11:54:27 -0700444 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson6b92c6e2015-11-06 13:11:16 -0800445 if (runningTask == null) return;
Winson6b92c6e2015-11-06 13:11:16 -0800446
447 // Find the task in the recents list
Winsond46b7272016-04-20 11:54:27 -0700448 boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
Winson250608a2015-11-24 15:00:31 -0800449 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson6b92c6e2015-11-06 13:11:16 -0800450 Task toTask = null;
451 ActivityOptions launchOpts = null;
452 int taskCount = tasks.size();
453 for (int i = taskCount - 1; i >= 1; i--) {
454 Task task = tasks.get(i);
Winsond46b7272016-04-20 11:54:27 -0700455 if (isRunningTaskInHomeStack) {
Winsone86deb82015-11-12 09:32:10 -0800456 toTask = tasks.get(i - 1);
457 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
458 R.anim.recents_launch_next_affiliated_task_target,
459 R.anim.recents_fast_toggle_app_home_exit);
460 break;
461 } else if (task.key.id == runningTask.id) {
Winson6b92c6e2015-11-06 13:11:16 -0800462 toTask = tasks.get(i - 1);
463 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
464 R.anim.recents_launch_prev_affiliated_task_target,
465 R.anim.recents_launch_prev_affiliated_task_source);
466 break;
467 }
468 }
469
470 // Return early if there is no next task
471 if (toTask == null) {
472 ssp.startInPlaceAnimationOnFrontMostApplication(
473 ActivityOptions.makeCustomInPlaceAnimation(mContext,
474 R.anim.recents_launch_prev_affiliated_task_bounce));
475 return;
476 }
477
478 // Launch the task
Vladislav Kaznacheev1f6b7092016-05-04 17:18:56 -0700479 ssp.startActivityFromRecents(
Jorim Jaggi34795e32017-05-12 17:27:46 +0200480 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
481 null /* resultListener */);
Winson6b92c6e2015-11-06 13:11:16 -0800482 }
483
484 /**
485 * Transitions to the next affiliated task.
486 */
487 public void showRelativeAffiliatedTask(boolean showNextTask) {
488 SystemServicesProxy ssp = Recents.getSystemServices();
Winsone7f138c2015-10-22 16:15:21 -0700489 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700490 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
Winsondfd7be02016-05-10 13:30:37 -0700491 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
Winson190fe3bf2015-10-20 14:57:24 -0700492 TaskStack focusedStack = plan.getTaskStack();
493
494 // Return early if there are no tasks in the focused stack
Winson4b057c62016-01-12 15:01:52 -0800495 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
Winson190fe3bf2015-10-20 14:57:24 -0700496
Winson190fe3bf2015-10-20 14:57:24 -0700497 // Return early if there is no running task (can't determine affiliated tasks in this case)
Winsond46b7272016-04-20 11:54:27 -0700498 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
Winson190fe3bf2015-10-20 14:57:24 -0700499 if (runningTask == null) return;
Matthew Ngae1ff4f2016-11-10 15:49:14 -0800500 // Return early if the running task is in the home/recents stack (optimization)
501 if (isHomeOrRecentsStack(runningTask.stackId)) return;
Winson190fe3bf2015-10-20 14:57:24 -0700502
503 // Find the task in the recents list
Winson250608a2015-11-24 15:00:31 -0800504 ArrayList<Task> tasks = focusedStack.getStackTasks();
Winson190fe3bf2015-10-20 14:57:24 -0700505 Task toTask = null;
506 ActivityOptions launchOpts = null;
507 int taskCount = tasks.size();
508 int numAffiliatedTasks = 0;
509 for (int i = 0; i < taskCount; i++) {
510 Task task = tasks.get(i);
511 if (task.key.id == runningTask.id) {
512 TaskGrouping group = task.group;
513 Task.TaskKey toTaskKey;
514 if (showNextTask) {
515 toTaskKey = group.getNextTaskInGroup(task);
516 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
517 R.anim.recents_launch_next_affiliated_task_target,
518 R.anim.recents_launch_next_affiliated_task_source);
519 } else {
520 toTaskKey = group.getPrevTaskInGroup(task);
521 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
522 R.anim.recents_launch_prev_affiliated_task_target,
523 R.anim.recents_launch_prev_affiliated_task_source);
524 }
525 if (toTaskKey != null) {
526 toTask = focusedStack.findTaskWithId(toTaskKey.id);
527 }
528 numAffiliatedTasks = group.getTaskCount();
529 break;
530 }
531 }
532
533 // Return early if there is no next task
534 if (toTask == null) {
535 if (numAffiliatedTasks > 1) {
536 if (showNextTask) {
Winsone7f138c2015-10-22 16:15:21 -0700537 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700538 ActivityOptions.makeCustomInPlaceAnimation(mContext,
539 R.anim.recents_launch_next_affiliated_task_bounce));
540 } else {
Winsone7f138c2015-10-22 16:15:21 -0700541 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700542 ActivityOptions.makeCustomInPlaceAnimation(mContext,
543 R.anim.recents_launch_prev_affiliated_task_bounce));
544 }
545 }
546 return;
547 }
548
549 // Keep track of actually launched affiliated tasks
550 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
551
552 // Launch the task
Vladislav Kaznacheev1f6b7092016-05-04 17:18:56 -0700553 ssp.startActivityFromRecents(
Jorim Jaggi34795e32017-05-12 17:27:46 +0200554 mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
555 null /* resultListener */);
Winson190fe3bf2015-10-20 14:57:24 -0700556 }
557
558 public void showNextAffiliatedTask() {
559 // Keep track of when the affiliated task is triggered
560 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
561 showRelativeAffiliatedTask(true);
562 }
563
564 public void showPrevAffiliatedTask() {
565 // Keep track of when the affiliated task is triggered
566 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
567 showRelativeAffiliatedTask(false);
568 }
569
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800570 public void dockTopTask(int topTaskId, int dragMode,
571 int stackCreateMode, Rect initialBounds) {
Jorim Jaggi75b25972015-10-21 14:51:10 +0200572 SystemServicesProxy ssp = Recents.getSystemServices();
Jorim Jaggi9511b0f2016-01-29 19:12:44 -0800573
574 // Make sure we inform DividerView before we actually start the activity so we can change
575 // the resize mode already.
Chong Zhange4fbd322016-03-01 14:44:03 -0800576 if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
Jorim Jaggi899327f2016-02-25 20:44:18 -0500577 EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
Chong Zhange4fbd322016-03-01 14:44:03 -0800578 showRecents(
579 false /* triggeredFromAltTab */,
580 dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
581 false /* animate */,
Jorim Jaggi681fc7b2016-04-14 22:02:39 -0700582 true /* launchedWhileDockingTask*/,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700583 false /* fromHome */,
584 DividerView.INVALID_RECENTS_GROW_TARGET);
Chong Zhange4fbd322016-03-01 14:44:03 -0800585 }
Jorim Jaggi75b25972015-10-21 14:51:10 +0200586 }
587
Winson190fe3bf2015-10-20 14:57:24 -0700588 /**
589 * Returns the preloaded load plan and invalidates it.
590 */
591 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
592 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
593 sInstanceLoadPlan = null;
594 return plan;
595 }
596
597 /**
Winson670ea712016-04-12 17:02:26 -0700598 * Reloads all the resources for the current configuration.
Winsonb94443d2016-01-07 15:34:13 -0800599 */
Winson670ea712016-04-12 17:02:26 -0700600 private void reloadResources() {
Winsonb94443d2016-01-07 15:34:13 -0800601 Resources res = mContext.getResources();
Winsonb94443d2016-01-07 15:34:13 -0800602
603 mStatusBarHeight = res.getDimensionPixelSize(
604 com.android.internal.R.dimen.status_bar_height);
605 mNavBarHeight = res.getDimensionPixelSize(
606 com.android.internal.R.dimen.navigation_bar_height);
607 mNavBarWidth = res.getDimensionPixelSize(
608 com.android.internal.R.dimen.navigation_bar_width);
Jorim Jaggi25160db2016-04-18 16:03:36 -0700609 mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
Winson21700932016-03-24 17:26:23 -0700610 R.dimen.recents_task_view_header_height,
611 R.dimen.recents_task_view_header_height,
612 R.dimen.recents_task_view_header_height,
613 R.dimen.recents_task_view_header_height_tablet_land,
614 R.dimen.recents_task_view_header_height,
Manu Cornetf78b11d2016-12-16 19:50:55 -0800615 R.dimen.recents_task_view_header_height_tablet_land,
616 R.dimen.recents_grid_task_view_header_height);
Matthew Ng384e60e2016-10-31 16:44:01 -0700617
618 LayoutInflater inflater = LayoutInflater.from(mContext);
619 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
620 null, false);
621 mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
Winsonb94443d2016-01-07 15:34:13 -0800622 }
623
Jorim Jaggicdef5912017-04-03 17:24:19 +0200624 private void updateDummyStackViewLayout(TaskStack stack, Rect windowRect) {
Winsone7f138c2015-10-22 16:15:21 -0700625 SystemServicesProxy ssp = Recents.getSystemServices();
Winsonfc48b072016-04-21 11:20:11 -0700626 Rect displayRect = ssp.getDisplayRect();
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800627 Rect systemInsets = new Rect();
628 ssp.getStableInsets(systemInsets);
Jorim Jaggicdef5912017-04-03 17:24:19 +0200629
Winsoncf9b8322016-03-31 15:36:07 -0700630 // When docked, the nav bar insets are consumed and the activity is measured without insets.
631 // However, the window bounds include the insets, so we need to subtract them here to make
632 // them identical.
633 if (ssp.hasDockedTask()) {
634 windowRect.bottom -= systemInsets.bottom;
635 systemInsets.bottom = 0;
636 }
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800637 calculateWindowStableInsets(systemInsets, windowRect);
638 windowRect.offsetTo(0, 0);
Winson190fe3bf2015-10-20 14:57:24 -0700639
Winson Chungf3cfa892017-04-24 12:23:47 -0700640 synchronized (mDummyStackView) {
641 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
Winson190fe3bf2015-10-20 14:57:24 -0700642
Winson Chungf3cfa892017-04-24 12:23:47 -0700643 // Rebind the header bar and draw it for the transition
644 stackLayout.setSystemInsets(systemInsets);
645 if (stack != null) {
646 stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
647 systemInsets.left, systemInsets.right, mTaskStackBounds);
648 stackLayout.reset();
649 stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
650 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
651 }
Jorim Jaggicdef5912017-04-03 17:24:19 +0200652 }
653 }
654
655 private Rect getWindowRect(Rect windowRectOverride) {
656 return windowRectOverride != null
657 ? new Rect(windowRectOverride)
658 : Recents.getSystemServices().getWindowRect();
659 }
660
661 /**
662 * Prepares the header bar layout for the next transition, if the task view bounds has changed
663 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
664 *
665 * @param stack the stack to initialize the stack layout with
666 * @param windowRectOverride the rectangle to use when calculating the stack state which can
667 * be different from the current window rect if recents is resizing
668 * while being launched
669 */
670 private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
671 Rect windowRect = getWindowRect(windowRectOverride);
Winson Chungf3cfa892017-04-24 12:23:47 -0700672 int taskViewWidth = 0;
673 boolean useGridLayout = false;
674 synchronized (mDummyStackView) {
675 useGridLayout = mDummyStackView.useGridLayout();
676 updateDummyStackViewLayout(stack, windowRect);
677 if (stack != null) {
678 TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
679 mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
680 // Get the width of a task view so that we know how wide to draw the header bar.
681 if (useGridLayout) {
682 TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
683 gridLayout.initialize(windowRect);
684 taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
685 stack.getTaskCount(), new TaskViewTransform(),
686 stackLayout).rect.width();
687 } else {
688 Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
689 if (!taskViewBounds.isEmpty()) {
690 taskViewWidth = taskViewBounds.width();
691 }
Manu Cornetde3cfb02017-01-25 18:31:01 +0900692 }
693 }
Winson Chungf3cfa892017-04-24 12:23:47 -0700694 }
Winson190fe3bf2015-10-20 14:57:24 -0700695
Winson Chungf3cfa892017-04-24 12:23:47 -0700696 if (stack != null && taskViewWidth > 0) {
697 synchronized (mHeaderBarLock) {
698 if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
699 mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
700 if (useGridLayout) {
701 mHeaderBar.setShouldDarkenBackgroundColor(true);
702 mHeaderBar.setNoUserInteractionState();
Winson94bc4f22016-04-07 14:22:12 -0700703 }
Winson Chungf3cfa892017-04-24 12:23:47 -0700704 mHeaderBar.forceLayout();
705 mHeaderBar.measure(
706 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
707 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
Winsonaeb298c2016-04-05 13:08:11 -0700708 }
Winson Chungf3cfa892017-04-24 12:23:47 -0700709 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
710 }
Winsonaeb298c2016-04-05 13:08:11 -0700711
Winson Chungf3cfa892017-04-24 12:23:47 -0700712 // Update the transition bitmap to match the new header bar height
713 if (mThumbTransitionBitmapCache == null ||
714 (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
715 (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
716 mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
717 mTaskBarHeight, Bitmap.Config.ARGB_8888);
Winsonaeb298c2016-04-05 13:08:11 -0700718 }
Winson190fe3bf2015-10-20 14:57:24 -0700719 }
720 }
721
722 /**
Jorim Jaggic6c89a82016-01-28 17:48:21 -0800723 * Given the stable insets and the rect for our window, calculates the insets that affect our
724 * window.
725 */
726 private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
727 Rect displayRect = Recents.getSystemServices().getDisplayRect();
728
729 // Display rect without insets - available app space
730 Rect appRect = new Rect(displayRect);
731 appRect.inset(inOutInsets);
732
733 // Our window intersected with available app space
734 Rect windowRectWithInsets = new Rect(windowRect);
735 windowRectWithInsets.intersect(appRect);
736 inOutInsets.left = windowRectWithInsets.left - windowRect.left;
737 inOutInsets.top = windowRectWithInsets.top - windowRect.top;
738 inOutInsets.right = windowRect.right - windowRectWithInsets.right;
739 inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
740 }
741
742 /**
Winson190fe3bf2015-10-20 14:57:24 -0700743 * Preloads the icon of a task.
744 */
Winsonc4a038a2016-05-26 16:42:15 -0700745 private void preloadIcon(int runningTaskId) {
Winson190fe3bf2015-10-20 14:57:24 -0700746 // Ensure that we load the running task's icon
747 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
Winsonc4a038a2016-05-26 16:42:15 -0700748 launchOpts.runningTaskId = runningTaskId;
Winson190fe3bf2015-10-20 14:57:24 -0700749 launchOpts.loadThumbnails = false;
750 launchOpts.onlyLoadForCache = true;
Winsone7f138c2015-10-22 16:15:21 -0700751 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700752 }
753
754 /**
Winson190fe3bf2015-10-20 14:57:24 -0700755 * Creates the activity options for a unknown state->recents transition.
756 */
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700757 protected ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700758 return ActivityOptions.makeCustomAnimation(mContext,
759 R.anim.recents_from_unknown_enter,
760 R.anim.recents_from_unknown_exit,
Winson3fb67562015-11-11 10:39:03 -0800761 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700762 }
763
764 /**
765 * Creates the activity options for a home->recents transition.
766 */
Winson008ee15f2016-03-18 17:17:25 -0700767 protected ActivityOptions getHomeTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700768 return ActivityOptions.makeCustomAnimation(mContext,
769 R.anim.recents_from_launcher_enter,
770 R.anim.recents_from_launcher_exit,
Winson3fb67562015-11-11 10:39:03 -0800771 mHandler, null);
Winson190fe3bf2015-10-20 14:57:24 -0700772 }
773
774 /**
775 * Creates the activity options for an app->recents transition.
776 */
777 private ActivityOptions getThumbnailTransitionActivityOptions(
Winson Chungf3cfa892017-04-24 12:23:47 -0700778 ActivityManager.RunningTaskInfo runningTask, Rect windowOverrideRect) {
Winsonc4a038a2016-05-26 16:42:15 -0700779 if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700780 ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
Winson Chungf3cfa892017-04-24 12:23:47 -0700781 ArrayList<Task> tasks;
782 TaskStackLayoutAlgorithm stackLayout;
783 TaskStackViewScroller stackScroller;
Winsone693aaf2016-03-01 12:05:59 -0800784
Winson Chungf3cfa892017-04-24 12:23:47 -0700785 synchronized (mDummyStackView) {
786 tasks = mDummyStackView.getStack().getStackTasks();
787 stackLayout = mDummyStackView.getStackAlgorithm();
788 stackScroller = mDummyStackView.getScroller();
789
790 mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
791 mDummyStackView.updateToInitialState();
792 }
Winsone693aaf2016-03-01 12:05:59 -0800793
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700794 for (int i = tasks.size() - 1; i >= 0; i--) {
795 Task task = tasks.get(i);
Winson387aac62015-11-25 11:18:56 -0800796 if (task.isFreeformTask()) {
Winsone693aaf2016-03-01 12:05:59 -0800797 mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700798 stackScroller.getStackScroll(), mTmpTransform, null,
Jiaquan He26f637b2016-12-27 14:44:14 -0800799 windowOverrideRect);
Winsonaeb298c2016-04-05 13:08:11 -0700800 Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
801 mThumbTransitionBitmapCache);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700802 Rect toTaskRect = new Rect();
803 mTmpTransform.rect.round(toTaskRect);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700804 specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
805 }
806 }
807 AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
808 specs.toArray(specsArray);
809 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3fb67562015-11-11 10:39:03 -0800810 specsArray, mHandler, null, this);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700811 } else {
812 // Update the destination rect
813 Task toTask = new Task();
Winson Chungf3cfa892017-04-24 12:23:47 -0700814 TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700815 windowOverrideRect);
Manu Cornet57b61492017-01-24 18:19:05 +0900816 Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
Manu Cornetfc6ac0a2017-01-18 15:42:14 -0800817 mThumbTransitionBitmapCache);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700818 if (thumbnail != null) {
Winsonaeb298c2016-04-05 13:08:11 -0700819 RectF toTaskRect = toTransform.rect;
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700820 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
821 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
Winson3fb67562015-11-11 10:39:03 -0800822 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
Filip Gruszczynskid64ef3e2015-10-27 17:58:02 -0700823 }
824 // If both the screenshot and thumbnail fails, then just fall back to the default transition
825 return getUnknownTransitionActivityOptions();
826 }
827 }
Winson190fe3bf2015-10-20 14:57:24 -0700828
Winson190fe3bf2015-10-20 14:57:24 -0700829 /**
830 * Returns the transition rect for the given task id.
831 */
Winsone693aaf2016-03-01 12:05:59 -0800832 private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700833 Task runningTaskOut, Rect windowOverrideRect) {
Winson190fe3bf2015-10-20 14:57:24 -0700834 // Find the running task in the TaskStack
Winsone693aaf2016-03-01 12:05:59 -0800835 TaskStack stack = stackView.getStack();
Winson65c851e2016-01-20 12:43:35 -0800836 Task launchTask = stack.getLaunchTarget();
837 if (launchTask != null) {
838 runningTaskOut.copyFrom(launchTask);
839 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700840 // If no task is specified or we can not find the task just use the front most one
Winson35a8b042016-01-22 09:41:09 -0800841 launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
Winson65c851e2016-01-20 12:43:35 -0800842 runningTaskOut.copyFrom(launchTask);
Winson190fe3bf2015-10-20 14:57:24 -0700843 }
844
845 // Get the transform for the running task
Winson003eda62016-03-11 14:56:00 -0800846 stackView.updateLayoutAlgorithm(true /* boundScroll */);
Winson67c79572016-04-13 14:02:18 -0700847 stackView.updateToInitialState();
Winson7845e8c2016-03-29 10:23:19 -0700848 stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
Jiaquan He26f637b2016-12-27 14:44:14 -0800849 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
Winson190fe3bf2015-10-20 14:57:24 -0700850 return mTmpTransform;
851 }
852
853 /**
854 * Draws the header of a task used for the window animation into a bitmap.
855 */
Winsonaeb298c2016-04-05 13:08:11 -0700856 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
857 Bitmap thumbnail) {
Winson8be16342016-02-09 11:53:27 -0800858 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700859 if (toTransform != null && toTask.key != null) {
Winson190fe3bf2015-10-20 14:57:24 -0700860 synchronized (mHeaderBarLock) {
Winson8be16342016-02-09 11:53:27 -0800861 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
Winson Chung509d0d02015-12-16 15:43:12 -0500862 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
863 (int) toTransform.rect.height());
Winsonc742f972015-11-12 11:32:21 -0800864 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson190fe3bf2015-10-20 14:57:24 -0700865 thumbnail.eraseColor(0xFFff0000);
866 } else {
Winsonaeb298c2016-04-05 13:08:11 -0700867 thumbnail.eraseColor(0);
Winson190fe3bf2015-10-20 14:57:24 -0700868 Canvas c = new Canvas(thumbnail);
Winson22574af2016-03-23 19:00:28 -0700869 // Workaround for b/27815919, reset the callback so that we do not trigger an
870 // invalidate on the header bar as a result of updating the icon
871 Drawable icon = mHeaderBar.getIconView().getDrawable();
872 if (icon != null) {
873 icon.setCallback(null);
874 }
Winsond2a03062016-04-15 11:19:07 -0700875 mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
Winson8be16342016-02-09 11:53:27 -0800876 disabledInSafeMode);
Winsond2a03062016-04-15 11:19:07 -0700877 mHeaderBar.onTaskDataLoaded();
Winsone693aaf2016-03-01 12:05:59 -0800878 mHeaderBar.setDimAlpha(toTransform.dimAlpha);
Winson190fe3bf2015-10-20 14:57:24 -0700879 mHeaderBar.draw(c);
880 c.setBitmap(null);
881 }
882 }
883 return thumbnail.createAshmemBitmap();
884 }
885 return null;
886 }
887
888 /**
889 * Shows the recents activity
890 */
Winsond46b7272016-04-20 11:54:27 -0700891 protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
892 boolean isHomeStackVisible, boolean animate, int growTarget) {
Winsone7f138c2015-10-22 16:15:21 -0700893 RecentsTaskLoader loader = Recents.getTaskLoader();
Winsonc5b12dd2016-03-23 20:25:12 -0700894 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
Winsonfc73eec2016-08-01 15:48:34 -0700895 SystemServicesProxy ssp = Recents.getSystemServices();
896 boolean isBlacklisted = (runningTask != null)
897 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
898 : false;
Winson190fe3bf2015-10-20 14:57:24 -0700899
Winsonfc73eec2016-08-01 15:48:34 -0700900 int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
Winsonc4a038a2016-05-26 16:42:15 -0700901 ? runningTask.id
902 : -1;
903
Winsone5f1faa2015-11-20 12:26:23 -0800904 // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
Jorim Jaggi435b2e42015-11-24 15:09:30 -0800905 // should always preload the tasks now. If we are dragging in recents, reload them as
906 // the stacks might have changed.
Winsonfc73eec2016-08-01 15:48:34 -0700907 if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
Winsone5f1faa2015-11-20 12:26:23 -0800908 // Create a new load plan if preloadRecents() was never triggered
Winson190fe3bf2015-10-20 14:57:24 -0700909 sInstanceLoadPlan = loader.createLoadPlan(mContext);
910 }
Jorim Jaggie161f082016-02-05 14:26:16 -0800911 if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
Winsonc4a038a2016-05-26 16:42:15 -0700912 loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
Winson190fe3bf2015-10-20 14:57:24 -0700913 }
Winsonc5b12dd2016-03-23 20:25:12 -0700914
Winson190fe3bf2015-10-20 14:57:24 -0700915 TaskStack stack = sInstanceLoadPlan.getTaskStack();
Winsonc5b12dd2016-03-23 20:25:12 -0700916 boolean hasRecentTasks = stack.getTaskCount() > 0;
Winsonfc73eec2016-08-01 15:48:34 -0700917 boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
918 hasRecentTasks;
Winson190fe3bf2015-10-20 14:57:24 -0700919
Winsonaeb298c2016-04-05 13:08:11 -0700920 // Update the launch state that we need in updateHeaderBarLayout()
Jorim Jaggic5887ea2016-05-13 18:21:48 -0700921 launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
Winsonaeb298c2016-04-05 13:08:11 -0700922 launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
Winsonfc73eec2016-08-01 15:48:34 -0700923 launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
Winsonaeb298c2016-04-05 13:08:11 -0700924 launchState.launchedViaDockGesture = mLaunchedWhileDocking;
925 launchState.launchedViaDragGesture = mDraggingInRecents;
Winsonc4a038a2016-05-26 16:42:15 -0700926 launchState.launchedToTaskId = runningTaskId;
Winsonaeb298c2016-04-05 13:08:11 -0700927 launchState.launchedWithAltTab = mTriggeredFromAltTab;
928
929 // Preload the icon (this will be a null-op if we have preloaded the icon already in
930 // preloadRecents())
Winsonc4a038a2016-05-26 16:42:15 -0700931 preloadIcon(runningTaskId);
Winsonaeb298c2016-04-05 13:08:11 -0700932
Winsonf0d1c442015-12-01 11:04:45 -0800933 // Update the header bar if necessary
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700934 Rect windowOverrideRect = getWindowRectOverride(growTarget);
935 updateHeaderBarLayout(stack, windowOverrideRect);
Winsonf0d1c442015-12-01 11:04:45 -0800936
Winson190fe3bf2015-10-20 14:57:24 -0700937 // Prepare the dummy stack for the transition
Winson Chungf3cfa892017-04-24 12:23:47 -0700938 TaskStackLayoutAlgorithm.VisibilityReport stackVr;
939 synchronized (mDummyStackView) {
940 stackVr = mDummyStackView.computeStackVisibilityReport();
941 }
Jorim Jaggibb42a462015-11-20 16:27:16 -0800942
Winsonaeb298c2016-04-05 13:08:11 -0700943 // Update the remaining launch state
Winsonc5b12dd2016-03-23 20:25:12 -0700944 launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
945 launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
Winsonc5b12dd2016-03-23 20:25:12 -0700946
Jorim Jaggibb42a462015-11-20 16:27:16 -0800947 if (!animate) {
Winsonc5b12dd2016-03-23 20:25:12 -0700948 startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
Jorim Jaggibb42a462015-11-20 16:27:16 -0800949 return;
950 }
951
Winsonaeb298c2016-04-05 13:08:11 -0700952 ActivityOptions opts;
Winsonfc73eec2016-08-01 15:48:34 -0700953 if (isBlacklisted) {
954 opts = getUnknownTransitionActivityOptions();
955 } else if (useThumbnailTransition) {
Winson190fe3bf2015-10-20 14:57:24 -0700956 // Try starting with a thumbnail transition
Winson Chungf3cfa892017-04-24 12:23:47 -0700957 opts = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
Winsonaeb298c2016-04-05 13:08:11 -0700958 } else {
Winson190fe3bf2015-10-20 14:57:24 -0700959 // If there is no thumbnail transition, but is launching from home into recents, then
Winson008ee15f2016-03-18 17:17:25 -0700960 // use a quick home transition
Winsonaeb298c2016-04-05 13:08:11 -0700961 opts = hasRecentTasks
962 ? getHomeTransitionActivityOptions()
963 : getUnknownTransitionActivityOptions();
Winson190fe3bf2015-10-20 14:57:24 -0700964 }
Winsonaeb298c2016-04-05 13:08:11 -0700965 startRecentsActivity(opts);
Winson190fe3bf2015-10-20 14:57:24 -0700966 mLastToggleTime = SystemClock.elapsedRealtime();
967 }
968
Jorim Jaggidb21bbd2016-04-18 15:32:07 -0700969 private Rect getWindowRectOverride(int growTarget) {
970 if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
971 return null;
972 }
973 Rect result = new Rect();
974 Rect displayRect = Recents.getSystemServices().getDisplayRect();
975 DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
976 result, displayRect.width(), displayRect.height(),
977 Recents.getSystemServices().getDockedDividerSize(mContext));
978 return result;
979 }
980
Winson190fe3bf2015-10-20 14:57:24 -0700981 /**
982 * Starts the recents activity.
983 */
Winsonc5b12dd2016-03-23 20:25:12 -0700984 private void startRecentsActivity(ActivityOptions opts) {
Winson190fe3bf2015-10-20 14:57:24 -0700985 Intent intent = new Intent();
Sid Soundararajan4bdb6872016-03-18 13:42:10 -0700986 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
Winson190fe3bf2015-10-20 14:57:24 -0700987 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
988 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
989 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
Sid Soundararajanb58c46a2016-01-26 15:39:27 -0800990
Winson190fe3bf2015-10-20 14:57:24 -0700991 if (opts != null) {
992 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
993 } else {
994 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
995 }
Jorim Jaggicdb06ca2016-01-25 19:15:12 -0800996 EventBus.getDefault().send(new RecentsActivityStartingEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700997 }
998
Winson3fb67562015-11-11 10:39:03 -0800999 /**** OnAnimationFinishedListener Implementation ****/
Filip Gruszczynski1a5203d2015-10-29 17:43:49 -07001000
1001 @Override
1002 public void onAnimationFinished() {
1003 EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
1004 }
Winson190fe3bf2015-10-20 14:57:24 -07001005}