OSDN Git Service

Merge "Refactor SystemServicesProxy to manage ITaskStackListener locally" into nyc-dev
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / recents / RecentsImpl.java
1 /*
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
17 package com.android.systemui.recents;
18
19 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
20
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.ActivityNotFoundException;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.graphics.Bitmap;
29 import android.graphics.Canvas;
30 import android.graphics.Rect;
31 import android.graphics.RectF;
32 import android.os.Handler;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.util.Log;
36 import android.util.MutableBoolean;
37 import android.view.AppTransitionAnimationSpec;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewConfiguration;
41
42 import com.android.internal.logging.MetricsLogger;
43 import com.android.systemui.Prefs;
44 import com.android.systemui.R;
45 import com.android.systemui.SystemUIApplication;
46 import com.android.systemui.recents.events.EventBus;
47 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
48 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
49 import com.android.systemui.recents.events.activity.HideRecentsEvent;
50 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
51 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
52 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
53 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
54 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
55 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
56 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
57 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
58 import com.android.systemui.recents.misc.DozeTrigger;
59 import com.android.systemui.recents.misc.ForegroundThread;
60 import com.android.systemui.recents.misc.SystemServicesProxy;
61 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
62 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
63 import com.android.systemui.recents.model.RecentsTaskLoader;
64 import com.android.systemui.recents.model.Task;
65 import com.android.systemui.recents.model.TaskGrouping;
66 import com.android.systemui.recents.model.TaskStack;
67 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
68 import com.android.systemui.recents.views.TaskStackView;
69 import com.android.systemui.recents.views.TaskStackViewScroller;
70 import com.android.systemui.recents.views.TaskViewHeader;
71 import com.android.systemui.recents.views.TaskViewTransform;
72 import com.android.systemui.statusbar.BaseStatusBar;
73 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
74 import com.android.systemui.statusbar.phone.PhoneStatusBar;
75
76 import java.util.ArrayList;
77
78 /**
79  * An implementation of the Recents component for the current user.  For secondary users, this can
80  * be called remotely from the system user.
81  */
82 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
83
84     private final static String TAG = "RecentsImpl";
85
86     // The minimum amount of time between each recents button press that we will handle
87     private final static int MIN_TOGGLE_DELAY_MS = 350;
88
89     // The duration within which the user releasing the alt tab (from when they pressed alt tab)
90     // that the fast alt-tab animation will run.  If the user's alt-tab takes longer than this
91     // duration, then we will toggle recents after this duration.
92     private final static int FAST_ALT_TAB_DELAY_MS = 225;
93
94     public final static String RECENTS_PACKAGE = "com.android.systemui";
95     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
96
97     /**
98      * An implementation of TaskStackListener, that allows us to listen for changes to the system
99      * task stacks and update recents accordingly.
100      */
101     class TaskStackListenerImpl extends TaskStackListener {
102         @Override
103         public void onTaskStackChanged() {
104             // Preloads the next task
105             RecentsConfiguration config = Recents.getConfiguration();
106             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
107                 RecentsTaskLoader loader = Recents.getTaskLoader();
108                 SystemServicesProxy ssp = Recents.getSystemServices();
109                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
110
111                 // Load the next task only if we aren't svelte
112                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
113                 loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
114                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
115                 // This callback is made when a new activity is launched and the old one is paused
116                 // so ignore the current activity and try and preload the thumbnail for the
117                 // previous one.
118                 if (runningTaskInfo != null) {
119                     launchOpts.runningTaskId = runningTaskInfo.id;
120                 }
121                 launchOpts.numVisibleTasks = 2;
122                 launchOpts.numVisibleTaskThumbnails = 2;
123                 launchOpts.onlyLoadForCache = true;
124                 launchOpts.onlyLoadPausedActivities = true;
125                 loader.loadTasks(mContext, plan, launchOpts);
126             }
127         }
128     }
129
130     protected static RecentsTaskLoadPlan sInstanceLoadPlan;
131
132     protected Context mContext;
133     protected Handler mHandler;
134     TaskStackListenerImpl mTaskStackListener;
135     RecentsAppWidgetHost mAppWidgetHost;
136     protected boolean mCanReuseTaskStackViews = true;
137     boolean mDraggingInRecents;
138     boolean mLaunchedWhileDocking;
139
140     // Task launching
141     Rect mSearchBarBounds = new Rect();
142     Rect mTaskStackBounds = new Rect();
143     Rect mLastTaskViewBounds = new Rect();
144     TaskViewTransform mTmpTransform = new TaskViewTransform();
145     int mStatusBarHeight;
146     int mNavBarHeight;
147     int mNavBarWidth;
148     int mTaskBarHeight;
149
150     // Header (for transition)
151     TaskViewHeader mHeaderBar;
152     final Object mHeaderBarLock = new Object();
153     protected TaskStackView mDummyStackView;
154
155     // Variables to keep track of if we need to start recents after binding
156     protected boolean mTriggeredFromAltTab;
157     protected long mLastToggleTime;
158     DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
159         @Override
160         public void run() {
161             // When this fires, then the user has not released alt-tab for at least
162             // FAST_ALT_TAB_DELAY_MS milliseconds
163             showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
164                     false /* reloadTasks */);
165         }
166     });
167
168     protected Bitmap mThumbnailTransitionBitmapCache;
169     Task mThumbnailTransitionBitmapCacheKey;
170
171     public RecentsImpl(Context context) {
172         mContext = context;
173         mHandler = new Handler();
174         mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
175
176         // Initialize the static foreground thread
177         ForegroundThread.get();
178
179         // Register the task stack listener
180         mTaskStackListener = new TaskStackListenerImpl();
181         SystemServicesProxy ssp = Recents.getSystemServices();
182         ssp.registerTaskStackListener(mTaskStackListener);
183
184         // Initialize the static configuration resources
185         reloadHeaderBarLayout();
186         updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
187
188         // When we start, preload the data associated with the previous recent tasks.
189         // We can use a new plan since the caches will be the same.
190         RecentsTaskLoader loader = Recents.getTaskLoader();
191         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
192         loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
193         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
194         launchOpts.numVisibleTasks = loader.getIconCacheSize();
195         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
196         launchOpts.onlyLoadForCache = true;
197         loader.loadTasks(mContext, plan, launchOpts);
198     }
199
200     public void onBootCompleted() {
201         updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
202     }
203
204     public void onConfigurationChanged() {
205         reloadHeaderBarLayout();
206         updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
207         // Don't reuse task stack views if the configuration changes
208         mCanReuseTaskStackViews = false;
209         Recents.getConfiguration().updateOnConfigurationChange();
210     }
211
212     /**
213      * This is only called from the system user's Recents.  Secondary users will instead proxy their
214      * visibility change events through to the system user via
215      * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
216      */
217     public void onVisibilityChanged(Context context, boolean visible) {
218         SystemUIApplication app = (SystemUIApplication) context;
219         PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
220         if (statusBar != null) {
221             statusBar.updateRecentsVisibility(visible);
222         }
223     }
224
225     /**
226      * This is only called from the system user's Recents.  Secondary users will instead proxy their
227      * visibility change events through to the system user via
228      * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
229      */
230     public void onStartScreenPinning(Context context) {
231         SystemUIApplication app = (SystemUIApplication) context;
232         PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
233         if (statusBar != null) {
234             statusBar.showScreenPinningRequest(false);
235         }
236     }
237
238     public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
239             boolean animate, boolean launchedWhileDockingTask) {
240         mTriggeredFromAltTab = triggeredFromAltTab;
241         mDraggingInRecents = draggingInRecents;
242         mLaunchedWhileDocking = launchedWhileDockingTask;
243         if (mFastAltTabTrigger.isAsleep()) {
244             // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
245             mFastAltTabTrigger.stopDozing();
246         } else if (mFastAltTabTrigger.isDozing()) {
247             // Fast alt-tab duration has not elapsed.  If this is triggered by a different
248             // showRecents() call, then ignore that call for now.
249             // TODO: We can not handle quick tabs that happen between the initial showRecents() call
250             //       that started the activity and the activity starting up.  The severity of this
251             //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
252             if (!triggeredFromAltTab) {
253                 return;
254             }
255             mFastAltTabTrigger.stopDozing();
256         } else if (triggeredFromAltTab) {
257             // The fast alt-tab detector is not yet running, so start the trigger and wait for the
258             // hideRecents() call, or for the fast alt-tab duration to elapse
259             mFastAltTabTrigger.startDozing();
260             return;
261         }
262
263         try {
264             // Check if the top task is in the home stack, and start the recents activity
265             SystemServicesProxy ssp = Recents.getSystemServices();
266             ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
267             MutableBoolean isTopTaskHome = new MutableBoolean(true);
268             if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
269                 startRecentsActivity(topTask, isTopTaskHome.value, animate);
270             }
271         } catch (ActivityNotFoundException e) {
272             Log.e(TAG, "Failed to launch RecentsActivity", e);
273         }
274     }
275
276     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
277         if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
278             // The user has released alt-tab before the trigger has run, so just show the next
279             // task immediately
280             showNextTask();
281
282             // Cancel the fast alt-tab trigger
283             mFastAltTabTrigger.stopDozing();
284             return;
285         }
286
287         // Defer to the activity to handle hiding recents, if it handles it, then it must still
288         // be visible
289         EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
290                 triggeredFromHomeKey));
291     }
292
293     public void toggleRecents() {
294         // Skip this toggle if we are already waiting to trigger recents via alt-tab
295         if (mFastAltTabTrigger.isDozing()) {
296             return;
297         }
298
299         mDraggingInRecents = false;
300         mLaunchedWhileDocking = false;
301         mTriggeredFromAltTab = false;
302
303         try {
304             SystemServicesProxy ssp = Recents.getSystemServices();
305             ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
306             MutableBoolean isTopTaskHome = new MutableBoolean(true);
307             long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
308
309             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
310                 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
311                 RecentsConfiguration config = Recents.getConfiguration();
312                 RecentsActivityLaunchState launchState = config.getLaunchState();
313                 if (!launchState.launchedWithAltTab) {
314                     // If the user taps quickly
315                     if (!debugFlags.isPagingEnabled() ||
316                             (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
317                                     elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
318                         // Launch the next focused task
319                         EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
320                     } else {
321                         // Notify recents to move onto the next task
322                         EventBus.getDefault().post(new IterateRecentsEvent());
323                     }
324                 } else {
325                     // If the user has toggled it too quickly, then just eat up the event here (it's
326                     // better than showing a janky screenshot).
327                     // NOTE: Ideally, the screenshot mechanism would take the window transform into
328                     // account
329                     if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
330                         return;
331                     }
332
333                     EventBus.getDefault().post(new ToggleRecentsEvent());
334                     mLastToggleTime = SystemClock.elapsedRealtime();
335                 }
336                 return;
337             } else {
338                 // If the user has toggled it too quickly, then just eat up the event here (it's
339                 // better than showing a janky screenshot).
340                 // NOTE: Ideally, the screenshot mechanism would take the window transform into
341                 // account
342                 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
343                     return;
344                 }
345
346                 // Otherwise, start the recents activity
347                 startRecentsActivity(topTask, isTopTaskHome.value, true /* animate */);
348
349                 // Only close the other system windows if we are actually showing recents
350                 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
351                 mLastToggleTime = SystemClock.elapsedRealtime();
352             }
353         } catch (ActivityNotFoundException e) {
354             Log.e(TAG, "Failed to launch RecentsActivity", e);
355         }
356     }
357
358     public void preloadRecents() {
359         // Preload only the raw task list into a new load plan (which will be consumed by the
360         // RecentsActivity) only if there is a task to animate to.
361         SystemServicesProxy ssp = Recents.getSystemServices();
362         ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
363         MutableBoolean topTaskHome = new MutableBoolean(true);
364         if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
365             RecentsTaskLoader loader = Recents.getTaskLoader();
366             sInstanceLoadPlan = loader.createLoadPlan(mContext);
367             sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
368             loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
369             TaskStack stack = sInstanceLoadPlan.getTaskStack();
370             if (stack.getTaskCount() > 0) {
371                 // We try and draw the thumbnail transition bitmap in parallel before
372                 // toggle/show recents is called
373                 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
374             }
375         }
376     }
377
378     public void cancelPreloadingRecents() {
379         // Do nothing
380     }
381
382     public void onDraggingInRecents(float distanceFromTop) {
383         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
384     }
385
386     public void onDraggingInRecentsEnded(float velocity) {
387         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
388     }
389
390     /**
391      * Transitions to the next recent task in the stack.
392      */
393     public void showNextTask() {
394         SystemServicesProxy ssp = Recents.getSystemServices();
395         RecentsTaskLoader loader = Recents.getTaskLoader();
396         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
397         loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
398         TaskStack focusedStack = plan.getTaskStack();
399
400         // Return early if there are no tasks in the focused stack
401         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
402
403         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
404         // Return early if there is no running task
405         if (runningTask == null) return;
406
407         // Find the task in the recents list
408         boolean isTopTaskHome = SystemServicesProxy.isHomeStack(runningTask.stackId);
409         ArrayList<Task> tasks = focusedStack.getStackTasks();
410         Task toTask = null;
411         ActivityOptions launchOpts = null;
412         int taskCount = tasks.size();
413         for (int i = taskCount - 1; i >= 1; i--) {
414             Task task = tasks.get(i);
415             if (isTopTaskHome) {
416                 toTask = tasks.get(i - 1);
417                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
418                         R.anim.recents_launch_next_affiliated_task_target,
419                         R.anim.recents_fast_toggle_app_home_exit);
420                 break;
421             } else if (task.key.id == runningTask.id) {
422                 toTask = tasks.get(i - 1);
423                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
424                         R.anim.recents_launch_prev_affiliated_task_target,
425                         R.anim.recents_launch_prev_affiliated_task_source);
426                 break;
427             }
428         }
429
430         // Return early if there is no next task
431         if (toTask == null) {
432             ssp.startInPlaceAnimationOnFrontMostApplication(
433                     ActivityOptions.makeCustomInPlaceAnimation(mContext,
434                             R.anim.recents_launch_prev_affiliated_task_bounce));
435             return;
436         }
437
438         // Launch the task
439         ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
440     }
441
442     /**
443      * Transitions to the next affiliated task.
444      */
445     public void showRelativeAffiliatedTask(boolean showNextTask) {
446         SystemServicesProxy ssp = Recents.getSystemServices();
447         RecentsTaskLoader loader = Recents.getTaskLoader();
448         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
449         loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
450         TaskStack focusedStack = plan.getTaskStack();
451
452         // Return early if there are no tasks in the focused stack
453         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
454
455         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
456         // Return early if there is no running task (can't determine affiliated tasks in this case)
457         if (runningTask == null) return;
458         // Return early if the running task is in the home stack (optimization)
459         if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
460
461         // Find the task in the recents list
462         ArrayList<Task> tasks = focusedStack.getStackTasks();
463         Task toTask = null;
464         ActivityOptions launchOpts = null;
465         int taskCount = tasks.size();
466         int numAffiliatedTasks = 0;
467         for (int i = 0; i < taskCount; i++) {
468             Task task = tasks.get(i);
469             if (task.key.id == runningTask.id) {
470                 TaskGrouping group = task.group;
471                 Task.TaskKey toTaskKey;
472                 if (showNextTask) {
473                     toTaskKey = group.getNextTaskInGroup(task);
474                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
475                             R.anim.recents_launch_next_affiliated_task_target,
476                             R.anim.recents_launch_next_affiliated_task_source);
477                 } else {
478                     toTaskKey = group.getPrevTaskInGroup(task);
479                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
480                             R.anim.recents_launch_prev_affiliated_task_target,
481                             R.anim.recents_launch_prev_affiliated_task_source);
482                 }
483                 if (toTaskKey != null) {
484                     toTask = focusedStack.findTaskWithId(toTaskKey.id);
485                 }
486                 numAffiliatedTasks = group.getTaskCount();
487                 break;
488             }
489         }
490
491         // Return early if there is no next task
492         if (toTask == null) {
493             if (numAffiliatedTasks > 1) {
494                 if (showNextTask) {
495                     ssp.startInPlaceAnimationOnFrontMostApplication(
496                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
497                                     R.anim.recents_launch_next_affiliated_task_bounce));
498                 } else {
499                     ssp.startInPlaceAnimationOnFrontMostApplication(
500                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
501                                     R.anim.recents_launch_prev_affiliated_task_bounce));
502                 }
503             }
504             return;
505         }
506
507         // Keep track of actually launched affiliated tasks
508         MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
509
510         // Launch the task
511         ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts);
512     }
513
514     public void showNextAffiliatedTask() {
515         // Keep track of when the affiliated task is triggered
516         MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
517         showRelativeAffiliatedTask(true);
518     }
519
520     public void showPrevAffiliatedTask() {
521         // Keep track of when the affiliated task is triggered
522         MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
523         showRelativeAffiliatedTask(false);
524     }
525
526     public void dockTopTask(int topTaskId, int dragMode,
527             int stackCreateMode, Rect initialBounds) {
528         SystemServicesProxy ssp = Recents.getSystemServices();
529
530         // Make sure we inform DividerView before we actually start the activity so we can change
531         // the resize mode already.
532         if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
533             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
534             showRecents(
535                     false /* triggeredFromAltTab */,
536                     dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
537                     false /* animate */,
538                     true /* launchedWhileDockingTask*/);
539         }
540     }
541
542     /**
543      * Returns the preloaded load plan and invalidates it.
544      */
545     public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
546         RecentsTaskLoadPlan plan = sInstanceLoadPlan;
547         sInstanceLoadPlan = null;
548         return plan;
549     }
550
551     /**
552      * Reloads all the layouts for the header bar transition.
553      */
554     private void reloadHeaderBarLayout() {
555         Resources res = mContext.getResources();
556         LayoutInflater inflater = LayoutInflater.from(mContext);
557
558         mStatusBarHeight = res.getDimensionPixelSize(
559                 com.android.internal.R.dimen.status_bar_height);
560         mNavBarHeight = res.getDimensionPixelSize(
561                 com.android.internal.R.dimen.navigation_bar_height);
562         mNavBarWidth = res.getDimensionPixelSize(
563                 com.android.internal.R.dimen.navigation_bar_width);
564         mTaskBarHeight = res.getDimensionPixelSize(
565                 R.dimen.recents_task_bar_height);
566         mDummyStackView = new TaskStackView(mContext);
567         mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
568                 null, false);
569     }
570
571     /**
572      * Prepares the header bar layout for the next transition, if the task view bounds has changed
573      * since the last call, it will attempt to re-measure and layout the header bar to the new size.
574      *
575      * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
576      *                               is not already bound (can be expensive)
577      * @param stack the stack to initialize the stack layout with
578      */
579     private void updateHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) {
580         RecentsConfiguration config = Recents.getConfiguration();
581         SystemServicesProxy ssp = Recents.getSystemServices();
582         Rect systemInsets = new Rect();
583         ssp.getStableInsets(systemInsets);
584         Rect windowRect = ssp.getWindowRect();
585         calculateWindowStableInsets(systemInsets, windowRect);
586         windowRect.offsetTo(0, 0);
587
588         // Update the configuration for the current state
589         config.update(systemInsets);
590
591         if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
592             // Try and pre-emptively bind the search widget on startup to ensure that we
593             // have the right thumbnail bounds to animate to.
594             // Note: We have to reload the widget id before we get the task stack bounds below
595             if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
596                 config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
597             }
598         }
599         config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
600                 mSearchBarBounds, mTaskStackBounds);
601
602         // Rebind the header bar and draw it for the transition
603         TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
604         Rect taskStackBounds = new Rect(mTaskStackBounds);
605         stackLayout.setSystemInsets(systemInsets);
606         if (stack != null) {
607             stackLayout.initialize(taskStackBounds,
608                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
609             mDummyStackView.setTasks(stack, false /* notifyStackChanges */,
610                     false /* relayoutTaskStack */);
611         }
612         Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
613         if (!taskViewBounds.equals(mLastTaskViewBounds)) {
614             mLastTaskViewBounds.set(taskViewBounds);
615
616             int taskViewWidth = taskViewBounds.width();
617             synchronized (mHeaderBarLock) {
618                 mHeaderBar.measure(
619                     View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
620                     View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
621                 mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
622             }
623         }
624     }
625
626     /**
627      * Given the stable insets and the rect for our window, calculates the insets that affect our
628      * window.
629      */
630     private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
631         Rect displayRect = Recents.getSystemServices().getDisplayRect();
632
633         // Display rect without insets - available app space
634         Rect appRect = new Rect(displayRect);
635         appRect.inset(inOutInsets);
636
637         // Our window intersected with available app space
638         Rect windowRectWithInsets = new Rect(windowRect);
639         windowRectWithInsets.intersect(appRect);
640         inOutInsets.left = windowRectWithInsets.left - windowRect.left;
641         inOutInsets.top = windowRectWithInsets.top - windowRect.top;
642         inOutInsets.right = windowRect.right - windowRectWithInsets.right;
643         inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
644     }
645
646     /**
647      * Preloads the icon of a task.
648      */
649     private void preloadIcon(ActivityManager.RunningTaskInfo task) {
650         // Ensure that we load the running task's icon
651         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
652         launchOpts.runningTaskId = task.id;
653         launchOpts.loadThumbnails = false;
654         launchOpts.onlyLoadForCache = true;
655         Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
656     }
657
658     /**
659      * Caches the header thumbnail used for a window animation asynchronously into
660      * {@link #mThumbnailTransitionBitmapCache}.
661      */
662     private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
663             TaskStack stack, TaskStackView stackView) {
664         preloadIcon(topTask);
665
666         // Update the header bar if necessary
667         updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
668
669         // Update the destination rect
670         final Task toTask = new Task();
671         final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
672         ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
673             @Override
674             public void run() {
675                 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
676                 mHandler.post(new Runnable() {
677                     @Override
678                     public void run() {
679                         mThumbnailTransitionBitmapCache = transitionBitmap;
680                         mThumbnailTransitionBitmapCacheKey = toTask;
681                     }
682                 });
683             }
684         });
685     }
686
687     /**
688      * Creates the activity options for a unknown state->recents transition.
689      */
690     protected ActivityOptions getUnknownTransitionActivityOptions() {
691         return ActivityOptions.makeCustomAnimation(mContext,
692                 R.anim.recents_from_unknown_enter,
693                 R.anim.recents_from_unknown_exit,
694                 mHandler, null);
695     }
696
697     /**
698      * Creates the activity options for a home->recents transition.
699      */
700     protected ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
701         if (fromSearchHome) {
702             return ActivityOptions.makeCustomAnimation(mContext,
703                     R.anim.recents_from_search_launcher_enter,
704                     R.anim.recents_from_search_launcher_exit,
705                     mHandler, null);
706         }
707         return ActivityOptions.makeCustomAnimation(mContext,
708                 R.anim.recents_from_launcher_enter,
709                 R.anim.recents_from_launcher_exit,
710                 mHandler, null);
711     }
712
713     /**
714      * Creates the activity options for an app->recents transition.
715      */
716     private ActivityOptions getThumbnailTransitionActivityOptions(
717             ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
718         if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
719             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
720             ArrayList<Task> tasks = stackView.getStack().getStackTasks();
721             TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
722             TaskStackViewScroller stackScroller = stackView.getScroller();
723
724             stackView.updateLayoutAlgorithm(true /* boundScroll */);
725             stackView.updateToInitialState();
726
727             for (int i = tasks.size() - 1; i >= 0; i--) {
728                 Task task = tasks.get(i);
729                 if (task.isFreeformTask()) {
730                     mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
731                                     stackScroller.getStackScroll(), mTmpTransform, null);
732                     Rect toTaskRect = new Rect();
733                     mTmpTransform.rect.round(toTaskRect);
734                     Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
735                     specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
736                 }
737             }
738             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
739             specs.toArray(specsArray);
740             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
741                     specsArray, mHandler, null, this);
742         } else {
743             // Update the destination rect
744             Task toTask = new Task();
745             TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
746             RectF toTaskRect = toTransform.rect;
747             Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
748             if (thumbnail != null) {
749                 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
750                         thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
751                         (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
752             }
753             // If both the screenshot and thumbnail fails, then just fall back to the default transition
754             return getUnknownTransitionActivityOptions();
755         }
756     }
757
758     private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
759             TaskViewTransform toTransform) {
760         Bitmap thumbnail;
761         if (mThumbnailTransitionBitmapCacheKey != null
762                 && mThumbnailTransitionBitmapCacheKey.key != null
763                 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
764             thumbnail = mThumbnailTransitionBitmapCache;
765             mThumbnailTransitionBitmapCacheKey = null;
766             mThumbnailTransitionBitmapCache = null;
767         } else {
768             preloadIcon(topTask);
769             thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
770         }
771         return thumbnail;
772     }
773
774     /**
775      * Returns the transition rect for the given task id.
776      */
777     private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
778             Task runningTaskOut) {
779         // Find the running task in the TaskStack
780         TaskStack stack = stackView.getStack();
781         Task launchTask = stack.getLaunchTarget();
782         if (launchTask != null) {
783             runningTaskOut.copyFrom(launchTask);
784         } else {
785             // If no task is specified or we can not find the task just use the front most one
786             launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
787             runningTaskOut.copyFrom(launchTask);
788         }
789
790         // Get the transform for the running task
791         stackView.updateLayoutAlgorithm(true /* boundScroll */);
792         stackView.updateToInitialState();
793         mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
794                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
795         return mTmpTransform;
796     }
797
798     /**
799      * Draws the header of a task used for the window animation into a bitmap.
800      */
801     private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
802         SystemServicesProxy ssp = Recents.getSystemServices();
803         if (toTransform != null && toTask.key != null) {
804             Bitmap thumbnail;
805             synchronized (mHeaderBarLock) {
806                 int toHeaderWidth = (int) toTransform.rect.width();
807                 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
808                 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
809                 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
810                         (int) toTransform.rect.height());
811                 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
812                         Bitmap.Config.ARGB_8888);
813                 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
814                     thumbnail.eraseColor(0xFFff0000);
815                 } else {
816                     Canvas c = new Canvas(thumbnail);
817                     c.scale(toTransform.scale, toTransform.scale);
818                     mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
819                             disabledInSafeMode);
820                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
821                     mHeaderBar.draw(c);
822                     c.setBitmap(null);
823                 }
824             }
825             return thumbnail.createAshmemBitmap();
826         }
827         return null;
828     }
829
830     /**
831      * Shows the recents activity
832      */
833     protected void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
834             boolean isTopTaskHome, boolean animate) {
835         RecentsTaskLoader loader = Recents.getTaskLoader();
836
837         // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
838         // should always preload the tasks now. If we are dragging in recents, reload them as
839         // the stacks might have changed.
840         if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
841             // Create a new load plan if preloadRecents() was never triggered
842             sInstanceLoadPlan = loader.createLoadPlan(mContext);
843         }
844         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
845             loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
846         }
847         TaskStack stack = sInstanceLoadPlan.getTaskStack();
848
849         // Update the header bar if necessary
850         updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
851
852         // Prepare the dummy stack for the transition
853         TaskStackLayoutAlgorithm.VisibilityReport stackVr =
854                 mDummyStackView.computeStackVisibilityReport();
855
856         if (!animate) {
857             ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
858             startRecentsActivity(topTask, opts, false /* fromHome */,
859                     false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
860             return;
861         }
862
863         boolean hasRecentTasks = stack.getTaskCount() > 0;
864         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
865
866         if (useThumbnailTransition) {
867             // Try starting with a thumbnail transition
868             ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
869             if (opts != null) {
870                 startRecentsActivity(topTask, opts, false /* fromHome */,
871                         false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
872             } else {
873                 // Fall through below to the non-thumbnail transition
874                 useThumbnailTransition = false;
875             }
876         }
877
878         if (!useThumbnailTransition) {
879             // If there is no thumbnail transition, but is launching from home into recents, then
880             // use a quick home transition and do the animation from home
881             if (hasRecentTasks) {
882                 SystemServicesProxy ssp = Recents.getSystemServices();
883                 String homeActivityPackage = ssp.getHomeActivityPackageName();
884                 String searchWidgetPackage = null;
885                 if (RecentsDebugFlags.Static.EnableSearchBar) {
886                     searchWidgetPackage = Prefs.getString(mContext,
887                             Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
888                 } else {
889                     AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
890                     if (searchWidgetInfo != null) {
891                         searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
892                     }
893                 }
894
895                 // Determine whether we are coming from a search owned home activity
896                 boolean fromSearchHome = (homeActivityPackage != null) &&
897                         homeActivityPackage.equals(searchWidgetPackage);
898                 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
899                 startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
900                         false /* fromThumbnail */, stackVr);
901             } else {
902                 // Otherwise we do the normal fade from an unknown source
903                 ActivityOptions opts = getUnknownTransitionActivityOptions();
904                 startRecentsActivity(topTask, opts, true /* fromHome */,
905                         false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
906             }
907         }
908         mLastToggleTime = SystemClock.elapsedRealtime();
909     }
910
911     /**
912      * Starts the recents activity.
913      */
914     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
915                 ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
916                 boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) {
917         // Update the configuration based on the launch options
918         RecentsConfiguration config = Recents.getConfiguration();
919         RecentsActivityLaunchState launchState = config.getLaunchState();
920         launchState.launchedFromHome = fromSearchHome || fromHome;
921         launchState.launchedFromSearchHome = fromSearchHome;
922         launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
923         launchState.launchedFromAppDocked = mLaunchedWhileDocking;
924         launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
925         launchState.launchedWithAltTab = mTriggeredFromAltTab;
926         launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
927         launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
928         launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
929         launchState.launchedHasConfigurationChanged = false;
930         launchState.launchedViaDragGesture = mDraggingInRecents;
931         launchState.launchedWhileDocking = mLaunchedWhileDocking;
932
933         Intent intent = new Intent();
934         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
935         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
936                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
937                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
938
939         if (opts != null) {
940             mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
941         } else {
942             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
943         }
944         mCanReuseTaskStackViews = true;
945         EventBus.getDefault().send(new RecentsActivityStartingEvent());
946     }
947
948     /**** OnAnimationFinishedListener Implementation ****/
949
950     @Override
951     public void onAnimationFinished() {
952         EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
953     }
954 }