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