blob: b05e1fe4adbaf6f303d2236de3d515bc43fd9703 [file] [log] [blame]
Winson190fe3bf2015-10-20 14:57:24 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents;
18
Winson190fe3bf2015-10-20 14:57:24 -070019import android.app.ActivityManager;
20import android.app.ActivityOptions;
21import android.app.ITaskStackListener;
22import android.content.ActivityNotFoundException;
Winson190fe3bf2015-10-20 14:57:24 -070023import android.content.Context;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.Rect;
Winson3150e572015-10-23 15:07:24 -070029import android.graphics.RectF;
Winson190fe3bf2015-10-20 14:57:24 -070030import android.os.Handler;
31import android.os.SystemClock;
32import android.os.UserHandle;
33import android.util.MutableBoolean;
34import android.view.LayoutInflater;
35import android.view.View;
36import com.android.internal.logging.MetricsLogger;
37import com.android.systemui.Prefs;
38import com.android.systemui.R;
39import com.android.systemui.SystemUIApplication;
Winson412e1802015-10-20 16:57:57 -070040import com.android.systemui.recents.events.EventBus;
41import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
42import com.android.systemui.recents.events.activity.HideRecentsEvent;
Winson0d14d4d2015-10-26 17:05:04 -070043import com.android.systemui.recents.events.activity.IterateRecentsEvent;
Winson412e1802015-10-20 16:57:57 -070044import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
Winson190fe3bf2015-10-20 14:57:24 -070045import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
46import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
47import com.android.systemui.recents.misc.Console;
Winsonab84fc52015-10-23 11:52:07 -070048import com.android.systemui.recents.misc.ForegroundThread;
Winson190fe3bf2015-10-20 14:57:24 -070049import com.android.systemui.recents.misc.SystemServicesProxy;
50import com.android.systemui.recents.model.RecentsTaskLoadPlan;
51import com.android.systemui.recents.model.RecentsTaskLoader;
52import com.android.systemui.recents.model.Task;
53import com.android.systemui.recents.model.TaskGrouping;
54import com.android.systemui.recents.model.TaskStack;
55import com.android.systemui.recents.views.TaskStackView;
56import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
57import com.android.systemui.recents.views.TaskViewHeader;
58import com.android.systemui.recents.views.TaskViewTransform;
59import com.android.systemui.statusbar.phone.PhoneStatusBar;
60
61import java.util.ArrayList;
62
63/**
64 * An implementation of the Recents component for the current user. For secondary users, this can
65 * be called remotely from the system user.
66 */
67public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
68 implements ActivityOptions.OnAnimationStartedListener {
69
70 private final static String TAG = "RecentsImpl";
Winsonab84fc52015-10-23 11:52:07 -070071 private final static boolean DEBUG = false;
Winson190fe3bf2015-10-20 14:57:24 -070072
Winson412e1802015-10-20 16:57:57 -070073 private final static int sMinToggleDelay = 350;
Winson190fe3bf2015-10-20 14:57:24 -070074
75 public final static String RECENTS_PACKAGE = "com.android.systemui";
76 public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
77
78
79 /**
80 * An implementation of ITaskStackListener, that allows us to listen for changes to the system
81 * task stacks and update recents accordingly.
82 */
83 class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
84 Handler mHandler;
85
86 public TaskStackListenerImpl(Handler handler) {
87 mHandler = handler;
88 }
89
90 @Override
91 public void onTaskStackChanged() {
92 // Debounce any task stack changes
93 mHandler.removeCallbacks(this);
94 mHandler.post(this);
95 }
96
97 /** Preloads the next task */
98 public void run() {
99 // TODO: Temporarily skip this if multi stack is enabled
100 /*
101 RecentsConfiguration config = RecentsConfiguration.getInstance();
102 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
Winsone7f138c2015-10-22 16:15:21 -0700103 RecentsTaskLoader loader = Recents.getTaskLoader();
104 SystemServicesProxy ssp = Recents.getSystemServices();
Winson190fe3bf2015-10-20 14:57:24 -0700105 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
106
107 // Load the next task only if we aren't svelte
108 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
109 loader.preloadTasks(plan, true);
110 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
111 // This callback is made when a new activity is launched and the old one is paused
112 // so ignore the current activity and try and preload the thumbnail for the
113 // previous one.
114 if (runningTaskInfo != null) {
115 launchOpts.runningTaskId = runningTaskInfo.id;
116 }
117 launchOpts.numVisibleTasks = 2;
118 launchOpts.numVisibleTaskThumbnails = 2;
119 launchOpts.onlyLoadForCache = true;
120 launchOpts.onlyLoadPausedActivities = true;
121 loader.loadTasks(mContext, plan, launchOpts);
122 }
123 */
124 }
125 }
126
Winsone7f138c2015-10-22 16:15:21 -0700127 private static RecentsTaskLoadPlan sInstanceLoadPlan;
Winson190fe3bf2015-10-20 14:57:24 -0700128
129 Context mContext;
Winson190fe3bf2015-10-20 14:57:24 -0700130 Handler mHandler;
131 TaskStackListenerImpl mTaskStackListener;
132 RecentsAppWidgetHost mAppWidgetHost;
133 boolean mBootCompleted;
134 boolean mStartAnimationTriggered;
135 boolean mCanReuseTaskStackViews = true;
136
137 // Task launching
138 RecentsConfiguration mConfig;
139 Rect mSearchBarBounds = new Rect();
140 Rect mTaskStackBounds = new Rect();
141 Rect mLastTaskViewBounds = new Rect();
142 TaskViewTransform mTmpTransform = new TaskViewTransform();
143 int mStatusBarHeight;
144 int mNavBarHeight;
145 int mNavBarWidth;
146 int mTaskBarHeight;
147
148 // Header (for transition)
149 TaskViewHeader mHeaderBar;
150 final Object mHeaderBarLock = new Object();
151 TaskStackView mDummyStackView;
152
153 // Variables to keep track of if we need to start recents after binding
154 boolean mTriggeredFromAltTab;
155 long mLastToggleTime;
156
157 Bitmap mThumbnailTransitionBitmapCache;
158 Task mThumbnailTransitionBitmapCacheKey;
159
160
161 public RecentsImpl(Context context) {
162 mContext = context;
Winson190fe3bf2015-10-20 14:57:24 -0700163 mHandler = new Handler();
164 mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
165 Resources res = mContext.getResources();
Winson190fe3bf2015-10-20 14:57:24 -0700166 LayoutInflater inflater = LayoutInflater.from(mContext);
167
Winsonab84fc52015-10-23 11:52:07 -0700168 // Initialize the static foreground thread
169 ForegroundThread.get();
170
Winson190fe3bf2015-10-20 14:57:24 -0700171 // Register the task stack listener
172 mTaskStackListener = new TaskStackListenerImpl(mHandler);
Winsone7f138c2015-10-22 16:15:21 -0700173 SystemServicesProxy ssp = Recents.getSystemServices();
174 ssp.registerTaskStackListener(mTaskStackListener);
Winson190fe3bf2015-10-20 14:57:24 -0700175
176 // Initialize the static configuration resources
Winsone7f138c2015-10-22 16:15:21 -0700177 mConfig = RecentsConfiguration.initialize(mContext, ssp);
Winson190fe3bf2015-10-20 14:57:24 -0700178 mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
179 mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
180 mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
181 mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
182 mDummyStackView = new TaskStackView(mContext, new TaskStack());
183 mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
184 null, false);
185 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
186
187 // When we start, preload the data associated with the previous recent tasks.
188 // We can use a new plan since the caches will be the same.
Winsone7f138c2015-10-22 16:15:21 -0700189 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700190 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
191 loader.preloadTasks(plan, true /* isTopTaskHome */);
192 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
193 launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
194 launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
195 launchOpts.onlyLoadForCache = true;
196 loader.loadTasks(mContext, plan, launchOpts);
197 }
198
199 public void onBootCompleted() {
200 mBootCompleted = true;
201 reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
202 }
203
204 @Override
205 public void onConfigurationChanged() {
206 // Don't reuse task stack views if the configuration changes
207 mCanReuseTaskStackViews = false;
208 mConfig.updateOnConfigurationChange();
209 }
210
211 /**
212 * This is only called from the system user's Recents. Secondary users will instead proxy their
213 * visibility change events through to the system user via
214 * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
215 */
216 public void onVisibilityChanged(Context context, boolean visible) {
217 SystemUIApplication app = (SystemUIApplication) context;
218 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
219 if (statusBar != null) {
220 statusBar.updateRecentsVisibility(visible);
221 }
222 }
223
224 /**
225 * This is only called from the system user's Recents. Secondary users will instead proxy their
226 * visibility change events through to the system user via
227 * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
228 */
229 public void onStartScreenPinning(Context context) {
230 SystemUIApplication app = (SystemUIApplication) context;
231 PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
232 if (statusBar != null) {
233 statusBar.showScreenPinningRequest(false);
234 }
235 }
236
237 @Override
238 public void showRecents(boolean triggeredFromAltTab) {
239 mTriggeredFromAltTab = triggeredFromAltTab;
240
241 try {
242 // Check if the top task is in the home stack, and start the recents activity
Winsone7f138c2015-10-22 16:15:21 -0700243 SystemServicesProxy ssp = Recents.getSystemServices();
244 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700245 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700246 if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Winson190fe3bf2015-10-20 14:57:24 -0700247 startRecentsActivity(topTask, isTopTaskHome.value);
248 }
249 } catch (ActivityNotFoundException e) {
250 Console.logRawError("Failed to launch RecentAppsIntent", e);
251 }
252 }
253
254 @Override
255 public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
256 if (mBootCompleted) {
257 // Defer to the activity to handle hiding recents, if it handles it, then it must still
258 // be visible
Winsone7f138c2015-10-22 16:15:21 -0700259 EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
Winson412e1802015-10-20 16:57:57 -0700260 triggeredFromHomeKey));
Winson190fe3bf2015-10-20 14:57:24 -0700261 }
262 }
263
264 @Override
265 public void toggleRecents() {
266 mTriggeredFromAltTab = false;
267
268 try {
Winsone7f138c2015-10-22 16:15:21 -0700269 SystemServicesProxy ssp = Recents.getSystemServices();
270 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700271 MutableBoolean isTopTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700272 if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
Winson0d14d4d2015-10-26 17:05:04 -0700273 if (Constants.DebugFlags.App.EnableFastToggleRecents) {
274 // Notify recents to move onto the next task
275 EventBus.getDefault().post(new IterateRecentsEvent());
276 } else {
277 // If the user has toggled it too quickly, then just eat up the event here (it's
278 // better than showing a janky screenshot).
279 // NOTE: Ideally, the screenshot mechanism would take the window transform into
280 // account
281 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
282 return;
283 }
284
285 EventBus.getDefault().post(new ToggleRecentsEvent());
286 mLastToggleTime = SystemClock.elapsedRealtime();
287 }
Winson190fe3bf2015-10-20 14:57:24 -0700288 return;
289 } else {
Winson0d14d4d2015-10-26 17:05:04 -0700290 // If the user has toggled it too quickly, then just eat up the event here (it's
291 // better than showing a janky screenshot).
292 // NOTE: Ideally, the screenshot mechanism would take the window transform into
293 // account
294 if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
295 return;
296 }
297
Winson190fe3bf2015-10-20 14:57:24 -0700298 // Otherwise, start the recents activity
299 startRecentsActivity(topTask, isTopTaskHome.value);
Winson0d14d4d2015-10-26 17:05:04 -0700300 mLastToggleTime = SystemClock.elapsedRealtime();
Winson190fe3bf2015-10-20 14:57:24 -0700301 }
302 } catch (ActivityNotFoundException e) {
303 Console.logRawError("Failed to launch RecentAppsIntent", e);
304 }
305 }
306
307 @Override
308 public void preloadRecents() {
309 // Preload only the raw task list into a new load plan (which will be consumed by the
310 // RecentsActivity) only if there is a task to animate to.
Winsone7f138c2015-10-22 16:15:21 -0700311 SystemServicesProxy ssp = Recents.getSystemServices();
312 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700313 MutableBoolean topTaskHome = new MutableBoolean(true);
Winsone7f138c2015-10-22 16:15:21 -0700314 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700315 sInstanceLoadPlan = loader.createLoadPlan(mContext);
Winsone7f138c2015-10-22 16:15:21 -0700316 if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
Winson190fe3bf2015-10-20 14:57:24 -0700317 sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
318 loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
319 TaskStack stack = sInstanceLoadPlan.getTaskStack();
320 if (stack.getTaskCount() > 0) {
Winsonab84fc52015-10-23 11:52:07 -0700321 // We try and draw the thumbnail transition bitmap in parallel before
322 // toggle/show recents is called
Winson190fe3bf2015-10-20 14:57:24 -0700323 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
324 }
325 }
326 }
327
328 @Override
329 public void cancelPreloadingRecents() {
330 // Do nothing
331 }
332
333 public void showRelativeAffiliatedTask(boolean showNextTask) {
334 // Return early if there is no focused stack
Winsone7f138c2015-10-22 16:15:21 -0700335 SystemServicesProxy ssp = Recents.getSystemServices();
336 int focusedStackId = ssp.getFocusedStack();
337 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700338 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
339 loader.preloadTasks(plan, true /* isTopTaskHome */);
340 TaskStack focusedStack = plan.getTaskStack();
341
342 // Return early if there are no tasks in the focused stack
343 if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
344
Winsone7f138c2015-10-22 16:15:21 -0700345 ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
Winson190fe3bf2015-10-20 14:57:24 -0700346 // Return early if there is no running task (can't determine affiliated tasks in this case)
347 if (runningTask == null) return;
348 // Return early if the running task is in the home stack (optimization)
Winson5510f6c2015-10-27 12:11:26 -0700349 if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
Winson190fe3bf2015-10-20 14:57:24 -0700350
351 // Find the task in the recents list
352 ArrayList<Task> tasks = focusedStack.getTasks();
353 Task toTask = null;
354 ActivityOptions launchOpts = null;
355 int taskCount = tasks.size();
356 int numAffiliatedTasks = 0;
357 for (int i = 0; i < taskCount; i++) {
358 Task task = tasks.get(i);
359 if (task.key.id == runningTask.id) {
360 TaskGrouping group = task.group;
361 Task.TaskKey toTaskKey;
362 if (showNextTask) {
363 toTaskKey = group.getNextTaskInGroup(task);
364 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
365 R.anim.recents_launch_next_affiliated_task_target,
366 R.anim.recents_launch_next_affiliated_task_source);
367 } else {
368 toTaskKey = group.getPrevTaskInGroup(task);
369 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
370 R.anim.recents_launch_prev_affiliated_task_target,
371 R.anim.recents_launch_prev_affiliated_task_source);
372 }
373 if (toTaskKey != null) {
374 toTask = focusedStack.findTaskWithId(toTaskKey.id);
375 }
376 numAffiliatedTasks = group.getTaskCount();
377 break;
378 }
379 }
380
381 // Return early if there is no next task
382 if (toTask == null) {
383 if (numAffiliatedTasks > 1) {
384 if (showNextTask) {
Winsone7f138c2015-10-22 16:15:21 -0700385 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700386 ActivityOptions.makeCustomInPlaceAnimation(mContext,
387 R.anim.recents_launch_next_affiliated_task_bounce));
388 } else {
Winsone7f138c2015-10-22 16:15:21 -0700389 ssp.startInPlaceAnimationOnFrontMostApplication(
Winson190fe3bf2015-10-20 14:57:24 -0700390 ActivityOptions.makeCustomInPlaceAnimation(mContext,
391 R.anim.recents_launch_prev_affiliated_task_bounce));
392 }
393 }
394 return;
395 }
396
397 // Keep track of actually launched affiliated tasks
398 MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
399
400 // Launch the task
401 if (toTask.isActive) {
402 // Bring an active task to the foreground
Winsone7f138c2015-10-22 16:15:21 -0700403 ssp.moveTaskToFront(toTask.key.id, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700404 } else {
Winsone7f138c2015-10-22 16:15:21 -0700405 ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700406 }
407 }
408
409 public void showNextAffiliatedTask() {
410 // Keep track of when the affiliated task is triggered
411 MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
412 showRelativeAffiliatedTask(true);
413 }
414
415 public void showPrevAffiliatedTask() {
416 // Keep track of when the affiliated task is triggered
417 MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
418 showRelativeAffiliatedTask(false);
419 }
420
Jorim Jaggi75b25972015-10-21 14:51:10 +0200421 public void dockTopTask() {
422 SystemServicesProxy ssp = Recents.getSystemServices();
423 ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
Winson38c96cb2015-10-27 18:25:02 -0700424 if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
Filip Gruszczynski90186c62015-10-26 14:07:00 -0700425 ssp.startTaskInDockedMode(topTask.id,
Jorim Jaggi75b25972015-10-21 14:51:10 +0200426 ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
427 showRecents(false /* triggeredFromAltTab */);
428 }
429 }
430
Winson190fe3bf2015-10-20 14:57:24 -0700431 /**
432 * Returns the preloaded load plan and invalidates it.
433 */
434 public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
435 RecentsTaskLoadPlan plan = sInstanceLoadPlan;
436 sInstanceLoadPlan = null;
437 return plan;
438 }
439
440 /**
441 * Prepares the header bar layout for the next transition, if the task view bounds has changed
442 * since the last call, it will attempt to re-measure and layout the header bar to the new size.
443 *
444 * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
445 * is not already bound (can be expensive)
446 */
447 private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
Winsone7f138c2015-10-22 16:15:21 -0700448 SystemServicesProxy ssp = Recents.getSystemServices();
449 Rect windowRect = ssp.getWindowRect();
Winson190fe3bf2015-10-20 14:57:24 -0700450
451 // Update the configuration for the current state
Winsone7f138c2015-10-22 16:15:21 -0700452 mConfig.update(mContext, ssp, ssp.getWindowRect());
Winson190fe3bf2015-10-20 14:57:24 -0700453
Winson2364f262015-10-26 10:56:59 -0700454 if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
Winson190fe3bf2015-10-20 14:57:24 -0700455 // Try and pre-emptively bind the search widget on startup to ensure that we
456 // have the right thumbnail bounds to animate to.
457 // Note: We have to reload the widget id before we get the task stack bounds below
Winsone7f138c2015-10-22 16:15:21 -0700458 if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
459 mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
Winson190fe3bf2015-10-20 14:57:24 -0700460 }
461 }
462 Rect systemInsets = new Rect(0, mStatusBarHeight,
463 (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
464 (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
465 mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
466 mSearchBarBounds, mTaskStackBounds);
467
468 // Rebind the header bar and draw it for the transition
469 TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
470 Rect taskStackBounds = new Rect(mTaskStackBounds);
471 algo.setSystemInsets(systemInsets);
472 algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
473 Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
474 if (!taskViewBounds.equals(mLastTaskViewBounds)) {
475 mLastTaskViewBounds.set(taskViewBounds);
476
477 int taskViewWidth = taskViewBounds.width();
478 synchronized (mHeaderBarLock) {
479 mHeaderBar.measure(
480 View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
481 View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
482 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
483 }
484 }
485 }
486
487 /**
488 * Preloads the icon of a task.
489 */
490 private void preloadIcon(ActivityManager.RunningTaskInfo task) {
Winson190fe3bf2015-10-20 14:57:24 -0700491 // Ensure that we load the running task's icon
492 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
493 launchOpts.runningTaskId = task.id;
494 launchOpts.loadThumbnails = false;
495 launchOpts.onlyLoadForCache = true;
Winsone7f138c2015-10-22 16:15:21 -0700496 Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
Winson190fe3bf2015-10-20 14:57:24 -0700497 }
498
499 /**
500 * Caches the header thumbnail used for a window animation asynchronously into
501 * {@link #mThumbnailTransitionBitmapCache}.
502 */
503 private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
504 TaskStack stack, TaskStackView stackView) {
505 preloadIcon(topTask);
506
507 // Update the header bar if necessary
508 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
509
510 // Update the destination rect
511 mDummyStackView.updateMinMaxScrollForStack(stack);
512 final Task toTask = new Task();
513 final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
514 topTask.id, toTask);
Winsonab84fc52015-10-23 11:52:07 -0700515 ForegroundThread.getHandler().post(new Runnable() {
Winson190fe3bf2015-10-20 14:57:24 -0700516 @Override
Winsonab84fc52015-10-23 11:52:07 -0700517 public void run() {
518 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
519 mHandler.post(new Runnable() {
520 @Override
521 public void run() {
522 mThumbnailTransitionBitmapCache = transitionBitmap;
523 mThumbnailTransitionBitmapCacheKey = toTask;
524 }
525 });
Winson190fe3bf2015-10-20 14:57:24 -0700526 }
Winsonab84fc52015-10-23 11:52:07 -0700527 });
Winson190fe3bf2015-10-20 14:57:24 -0700528 }
529
530 /**
531 * Creates the activity options for a unknown state->recents transition.
532 */
533 private ActivityOptions getUnknownTransitionActivityOptions() {
Winson190fe3bf2015-10-20 14:57:24 -0700534 return ActivityOptions.makeCustomAnimation(mContext,
535 R.anim.recents_from_unknown_enter,
536 R.anim.recents_from_unknown_exit,
537 mHandler, this);
538 }
539
540 /**
541 * Creates the activity options for a home->recents transition.
542 */
543 private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
Winson190fe3bf2015-10-20 14:57:24 -0700544 if (fromSearchHome) {
545 return ActivityOptions.makeCustomAnimation(mContext,
546 R.anim.recents_from_search_launcher_enter,
547 R.anim.recents_from_search_launcher_exit,
548 mHandler, this);
549 }
550 return ActivityOptions.makeCustomAnimation(mContext,
551 R.anim.recents_from_launcher_enter,
552 R.anim.recents_from_launcher_exit,
553 mHandler, this);
554 }
555
556 /**
557 * Creates the activity options for an app->recents transition.
558 */
559 private ActivityOptions getThumbnailTransitionActivityOptions(
560 ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
561
562 // Update the destination rect
563 Task toTask = new Task();
564 TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
565 topTask.id, toTask);
Winson3150e572015-10-23 15:07:24 -0700566 RectF toTaskRect = toTransform.rect;
Winson190fe3bf2015-10-20 14:57:24 -0700567 Bitmap thumbnail;
568 if (mThumbnailTransitionBitmapCacheKey != null
569 && mThumbnailTransitionBitmapCacheKey.key != null
570 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
571 thumbnail = mThumbnailTransitionBitmapCache;
572 mThumbnailTransitionBitmapCacheKey = null;
573 mThumbnailTransitionBitmapCache = null;
574 } else {
575 preloadIcon(topTask);
576 thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
577 }
578 if (thumbnail != null) {
Winson190fe3bf2015-10-20 14:57:24 -0700579 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
Winson3150e572015-10-23 15:07:24 -0700580 thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
581 (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
Winson190fe3bf2015-10-20 14:57:24 -0700582 }
583
584 // If both the screenshot and thumbnail fails, then just fall back to the default transition
585 return getUnknownTransitionActivityOptions();
586 }
587
588 /**
589 * Returns the transition rect for the given task id.
590 */
591 private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
592 TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
593 // Find the running task in the TaskStack
594 Task task = null;
595 ArrayList<Task> tasks = stack.getTasks();
596 if (runningTaskId != -1) {
597 // Otherwise, try and find the task with the
598 int taskCount = tasks.size();
599 for (int i = taskCount - 1; i >= 0; i--) {
600 Task t = tasks.get(i);
601 if (t.key.id == runningTaskId) {
602 task = t;
603 runningTaskOut.copyFrom(t);
604 break;
605 }
606 }
607 }
608 if (task == null) {
609 // If no task is specified or we can not find the task just use the front most one
610 task = tasks.get(tasks.size() - 1);
611 runningTaskOut.copyFrom(task);
612 }
613
614 // Get the transform for the running task
615 stackView.getScroller().setStackScrollToInitialState();
616 mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
617 stackView.getScroller().getStackScroll(), mTmpTransform, null);
618 return mTmpTransform;
619 }
620
621 /**
622 * Draws the header of a task used for the window animation into a bitmap.
623 */
624 private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
625 if (toTransform != null && toTask.key != null) {
626 Bitmap thumbnail;
627 synchronized (mHeaderBarLock) {
628 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
629 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
630 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
631 Bitmap.Config.ARGB_8888);
632 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
633 thumbnail.eraseColor(0xFFff0000);
634 } else {
635 Canvas c = new Canvas(thumbnail);
636 c.scale(toTransform.scale, toTransform.scale);
637 mHeaderBar.rebindToTask(toTask);
638 mHeaderBar.draw(c);
639 c.setBitmap(null);
640 }
641 }
642 return thumbnail.createAshmemBitmap();
643 }
644 return null;
645 }
646
647 /**
648 * Shows the recents activity
649 */
650 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
651 boolean isTopTaskHome) {
Winsone7f138c2015-10-22 16:15:21 -0700652 SystemServicesProxy ssp = Recents.getSystemServices();
653 RecentsTaskLoader loader = Recents.getTaskLoader();
Winson190fe3bf2015-10-20 14:57:24 -0700654
655 // Update the header bar if necessary
656 reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
657
658 if (sInstanceLoadPlan == null) {
659 // Create a new load plan if onPreloadRecents() was never triggered
660 sInstanceLoadPlan = loader.createLoadPlan(mContext);
661 }
662
663 if (!sInstanceLoadPlan.hasTasks()) {
664 loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
665 }
666 TaskStack stack = sInstanceLoadPlan.getTaskStack();
667
668 // Prepare the dummy stack for the transition
669 mDummyStackView.updateMinMaxScrollForStack(stack);
670 TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
671 mDummyStackView.computeStackVisibilityReport();
672 boolean hasRecentTasks = stack.getTaskCount() > 0;
673 boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
674
675 if (useThumbnailTransition) {
676 // Try starting with a thumbnail transition
677 ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
678 mDummyStackView);
679 if (opts != null) {
680 startRecentsActivity(topTask, opts, false /* fromHome */,
681 false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
682 } else {
683 // Fall through below to the non-thumbnail transition
684 useThumbnailTransition = false;
685 }
686 }
687
688 if (!useThumbnailTransition) {
689 // If there is no thumbnail transition, but is launching from home into recents, then
690 // use a quick home transition and do the animation from home
Winson2364f262015-10-26 10:56:59 -0700691 if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
Winsone7f138c2015-10-22 16:15:21 -0700692 String homeActivityPackage = ssp.getHomeActivityPackageName();
693 String searchWidgetPackage = Prefs.getString(mContext,
694 Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
Winson190fe3bf2015-10-20 14:57:24 -0700695
696 // Determine whether we are coming from a search owned home activity
697 boolean fromSearchHome = (homeActivityPackage != null) &&
698 homeActivityPackage.equals(searchWidgetPackage);
699 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
700 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
701 false /* fromThumbnail */, stackVr);
702 } else {
703 // Otherwise we do the normal fade from an unknown source
704 ActivityOptions opts = getUnknownTransitionActivityOptions();
705 startRecentsActivity(topTask, opts, true /* fromHome */,
706 false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
707 }
708 }
709 mLastToggleTime = SystemClock.elapsedRealtime();
710 }
711
712 /**
713 * Starts the recents activity.
714 */
715 private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
716 ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
717 TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
Winsone7f138c2015-10-22 16:15:21 -0700718 mStartAnimationTriggered = false;
719
Winson190fe3bf2015-10-20 14:57:24 -0700720 // Update the configuration based on the launch options
721 RecentsActivityLaunchState launchState = mConfig.getLaunchState();
722 launchState.launchedFromHome = fromSearchHome || fromHome;
723 launchState.launchedFromSearchHome = fromSearchHome;
724 launchState.launchedFromAppWithThumbnail = fromThumbnail;
725 launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
726 launchState.launchedWithAltTab = mTriggeredFromAltTab;
727 launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
728 launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
729 launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
730 launchState.launchedHasConfigurationChanged = false;
731
732 Intent intent = new Intent();
733 intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
734 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
735 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
736 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
737 if (opts != null) {
738 mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
739 } else {
740 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
741 }
742 mCanReuseTaskStackViews = true;
743 }
744
Winson190fe3bf2015-10-20 14:57:24 -0700745 /**** OnAnimationStartedListener Implementation ****/
746
747 @Override
748 public void onAnimationStarted() {
749 // Notify recents to start the enter animation
Winson190fe3bf2015-10-20 14:57:24 -0700750 if (!mStartAnimationTriggered) {
Winson412e1802015-10-20 16:57:57 -0700751 mStartAnimationTriggered = true;
Winsone7f138c2015-10-22 16:15:21 -0700752 EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
Winson190fe3bf2015-10-20 14:57:24 -0700753 }
754 }
755}