RecentsImpl.java revision 90186c6d6e7589835db183f602fb48e23a759a87
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.content.ActivityNotFoundException;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.graphics.RectF;
30import android.os.Handler;
31import android.os.SystemClock;
32import android.os.UserHandle;
33import android.util.MutableBoolean;
34import android.view.LayoutInflater;
35import android.view.View;
36import com.android.internal.logging.MetricsLogger;
37import com.android.systemui.Prefs;
38import com.android.systemui.R;
39import com.android.systemui.SystemUIApplication;
40import com.android.systemui.recents.events.EventBus;
41import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
42import com.android.systemui.recents.events.activity.HideRecentsEvent;
43import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
44import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
45import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
46import com.android.systemui.recents.misc.Console;
47import com.android.systemui.recents.misc.ForegroundThread;
48import com.android.systemui.recents.misc.SystemServicesProxy;
49import com.android.systemui.recents.model.RecentsTaskLoadPlan;
50import com.android.systemui.recents.model.RecentsTaskLoader;
51import com.android.systemui.recents.model.Task;
52import com.android.systemui.recents.model.TaskGrouping;
53import com.android.systemui.recents.model.TaskStack;
54import com.android.systemui.recents.views.TaskStackView;
55import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
56import com.android.systemui.recents.views.TaskViewHeader;
57import com.android.systemui.recents.views.TaskViewTransform;
58import com.android.systemui.statusbar.phone.PhoneStatusBar;
59
60import java.util.ArrayList;
61
62/**
63 * An implementation of the Recents component for the current user.  For secondary users, this can
64 * be called remotely from the system user.
65 */
66public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
67        implements ActivityOptions.OnAnimationStartedListener {
68
69    private final static String TAG = "RecentsImpl";
70    private final static boolean DEBUG = false;
71
72    private final static int sMinToggleDelay = 350;
73
74    public final static String RECENTS_PACKAGE = "com.android.systemui";
75    public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
76
77
78    /**
79     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
80     * task stacks and update recents accordingly.
81     */
82    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
83        Handler mHandler;
84
85        public TaskStackListenerImpl(Handler handler) {
86            mHandler = handler;
87        }
88
89        @Override
90        public void onTaskStackChanged() {
91            // Debounce any task stack changes
92            mHandler.removeCallbacks(this);
93            mHandler.post(this);
94        }
95
96        /** Preloads the next task */
97        public void run() {
98            // TODO: Temporarily skip this if multi stack is enabled
99            /*
100            RecentsConfiguration config = RecentsConfiguration.getInstance();
101            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
102                RecentsTaskLoader loader = Recents.getTaskLoader();
103                SystemServicesProxy ssp = Recents.getSystemServices();
104                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
105
106                // Load the next task only if we aren't svelte
107                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
108                loader.preloadTasks(plan, true);
109                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
110                // This callback is made when a new activity is launched and the old one is paused
111                // so ignore the current activity and try and preload the thumbnail for the
112                // previous one.
113                if (runningTaskInfo != null) {
114                    launchOpts.runningTaskId = runningTaskInfo.id;
115                }
116                launchOpts.numVisibleTasks = 2;
117                launchOpts.numVisibleTaskThumbnails = 2;
118                launchOpts.onlyLoadForCache = true;
119                launchOpts.onlyLoadPausedActivities = true;
120                loader.loadTasks(mContext, plan, launchOpts);
121            }
122            */
123        }
124    }
125
126    private static RecentsTaskLoadPlan sInstanceLoadPlan;
127
128    Context mContext;
129    Handler mHandler;
130    TaskStackListenerImpl mTaskStackListener;
131    RecentsAppWidgetHost mAppWidgetHost;
132    boolean mBootCompleted;
133    boolean mStartAnimationTriggered;
134    boolean mCanReuseTaskStackViews = true;
135
136    // Task launching
137    RecentsConfiguration mConfig;
138    Rect mSearchBarBounds = new Rect();
139    Rect mTaskStackBounds = new Rect();
140    Rect mLastTaskViewBounds = new Rect();
141    TaskViewTransform mTmpTransform = new TaskViewTransform();
142    int mStatusBarHeight;
143    int mNavBarHeight;
144    int mNavBarWidth;
145    int mTaskBarHeight;
146
147    // Header (for transition)
148    TaskViewHeader mHeaderBar;
149    final Object mHeaderBarLock = new Object();
150    TaskStackView mDummyStackView;
151
152    // Variables to keep track of if we need to start recents after binding
153    boolean mTriggeredFromAltTab;
154    long mLastToggleTime;
155
156    Bitmap mThumbnailTransitionBitmapCache;
157    Task mThumbnailTransitionBitmapCacheKey;
158
159
160    public RecentsImpl(Context context) {
161        mContext = context;
162        mHandler = new Handler();
163        mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
164        Resources res = mContext.getResources();
165        LayoutInflater inflater = LayoutInflater.from(mContext);
166
167        // Initialize the static foreground thread
168        ForegroundThread.get();
169
170        // Register the task stack listener
171        mTaskStackListener = new TaskStackListenerImpl(mHandler);
172        SystemServicesProxy ssp = Recents.getSystemServices();
173        ssp.registerTaskStackListener(mTaskStackListener);
174
175        // Initialize the static configuration resources
176        mConfig = RecentsConfiguration.initialize(mContext, ssp);
177        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
178        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
179        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
180        mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
181        mDummyStackView = new TaskStackView(mContext, new TaskStack());
182        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
183                null, false);
184        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
185
186        // When we start, preload the data associated with the previous recent tasks.
187        // We can use a new plan since the caches will be the same.
188        RecentsTaskLoader loader = Recents.getTaskLoader();
189        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
190        loader.preloadTasks(plan, true /* isTopTaskHome */);
191        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
192        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
193        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
194        launchOpts.onlyLoadForCache = true;
195        loader.loadTasks(mContext, plan, launchOpts);
196    }
197
198    public void onBootCompleted() {
199        mBootCompleted = true;
200        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
201    }
202
203    @Override
204    public void onConfigurationChanged() {
205        // Don't reuse task stack views if the configuration changes
206        mCanReuseTaskStackViews = false;
207        mConfig.updateOnConfigurationChange();
208    }
209
210    /**
211     * This is only called from the system user's Recents.  Secondary users will instead proxy their
212     * visibility change events through to the system user via
213     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
214     */
215    public void onVisibilityChanged(Context context, boolean visible) {
216        SystemUIApplication app = (SystemUIApplication) context;
217        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
218        if (statusBar != null) {
219            statusBar.updateRecentsVisibility(visible);
220        }
221    }
222
223    /**
224     * This is only called from the system user's Recents.  Secondary users will instead proxy their
225     * visibility change events through to the system user via
226     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
227     */
228    public void onStartScreenPinning(Context context) {
229        SystemUIApplication app = (SystemUIApplication) context;
230        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
231        if (statusBar != null) {
232            statusBar.showScreenPinningRequest(false);
233        }
234    }
235
236    @Override
237    public void showRecents(boolean triggeredFromAltTab) {
238        mTriggeredFromAltTab = triggeredFromAltTab;
239
240        try {
241            // Check if the top task is in the home stack, and start the recents activity
242            SystemServicesProxy ssp = Recents.getSystemServices();
243            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
244            MutableBoolean isTopTaskHome = new MutableBoolean(true);
245            if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
246                startRecentsActivity(topTask, isTopTaskHome.value);
247            }
248        } catch (ActivityNotFoundException e) {
249            Console.logRawError("Failed to launch RecentAppsIntent", e);
250        }
251    }
252
253    @Override
254    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
255        if (mBootCompleted) {
256            // Defer to the activity to handle hiding recents, if it handles it, then it must still
257            // be visible
258            EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
259                    triggeredFromHomeKey));
260        }
261    }
262
263    @Override
264    public void toggleRecents() {
265        mTriggeredFromAltTab = false;
266
267        try {
268            // If the user has toggled it too quickly, then just eat up the event here (it's better
269            // than showing a janky screenshot).
270            // NOTE: Ideally, the screenshot mechanism would take the window transform into account
271            if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
272                return;
273            }
274
275            // If Recents is the front most activity, then we should just communicate with it
276            // directly to launch the first task or dismiss itself
277            SystemServicesProxy ssp = Recents.getSystemServices();
278            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
279            MutableBoolean isTopTaskHome = new MutableBoolean(true);
280            if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
281                // Notify recents to toggle itself
282                EventBus.getDefault().post(new ToggleRecentsEvent());
283                mLastToggleTime = SystemClock.elapsedRealtime();
284                return;
285            } else {
286                // Otherwise, start the recents activity
287                startRecentsActivity(topTask, isTopTaskHome.value);
288            }
289        } catch (ActivityNotFoundException e) {
290            Console.logRawError("Failed to launch RecentAppsIntent", e);
291        }
292    }
293
294    @Override
295    public void preloadRecents() {
296        // Preload only the raw task list into a new load plan (which will be consumed by the
297        // RecentsActivity) only if there is a task to animate to.
298        SystemServicesProxy ssp = Recents.getSystemServices();
299        ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
300        MutableBoolean topTaskHome = new MutableBoolean(true);
301        RecentsTaskLoader loader = Recents.getTaskLoader();
302        sInstanceLoadPlan = loader.createLoadPlan(mContext);
303        if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
304            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
305            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
306            TaskStack stack = sInstanceLoadPlan.getTaskStack();
307            if (stack.getTaskCount() > 0) {
308                // We try and draw the thumbnail transition bitmap in parallel before
309                // toggle/show recents is called
310                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
311            }
312        }
313    }
314
315    @Override
316    public void cancelPreloadingRecents() {
317        // Do nothing
318    }
319
320    public void showRelativeAffiliatedTask(boolean showNextTask) {
321        // Return early if there is no focused stack
322        SystemServicesProxy ssp = Recents.getSystemServices();
323        int focusedStackId = ssp.getFocusedStack();
324        RecentsTaskLoader loader = Recents.getTaskLoader();
325        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
326        loader.preloadTasks(plan, true /* isTopTaskHome */);
327        TaskStack focusedStack = plan.getTaskStack();
328
329        // Return early if there are no tasks in the focused stack
330        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
331
332        ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
333        // Return early if there is no running task (can't determine affiliated tasks in this case)
334        if (runningTask == null) return;
335        // Return early if the running task is in the home stack (optimization)
336        if (ssp.isInHomeStack(runningTask.id)) return;
337
338        // Find the task in the recents list
339        ArrayList<Task> tasks = focusedStack.getTasks();
340        Task toTask = null;
341        ActivityOptions launchOpts = null;
342        int taskCount = tasks.size();
343        int numAffiliatedTasks = 0;
344        for (int i = 0; i < taskCount; i++) {
345            Task task = tasks.get(i);
346            if (task.key.id == runningTask.id) {
347                TaskGrouping group = task.group;
348                Task.TaskKey toTaskKey;
349                if (showNextTask) {
350                    toTaskKey = group.getNextTaskInGroup(task);
351                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
352                            R.anim.recents_launch_next_affiliated_task_target,
353                            R.anim.recents_launch_next_affiliated_task_source);
354                } else {
355                    toTaskKey = group.getPrevTaskInGroup(task);
356                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
357                            R.anim.recents_launch_prev_affiliated_task_target,
358                            R.anim.recents_launch_prev_affiliated_task_source);
359                }
360                if (toTaskKey != null) {
361                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
362                }
363                numAffiliatedTasks = group.getTaskCount();
364                break;
365            }
366        }
367
368        // Return early if there is no next task
369        if (toTask == null) {
370            if (numAffiliatedTasks > 1) {
371                if (showNextTask) {
372                    ssp.startInPlaceAnimationOnFrontMostApplication(
373                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
374                                    R.anim.recents_launch_next_affiliated_task_bounce));
375                } else {
376                    ssp.startInPlaceAnimationOnFrontMostApplication(
377                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
378                                    R.anim.recents_launch_prev_affiliated_task_bounce));
379                }
380            }
381            return;
382        }
383
384        // Keep track of actually launched affiliated tasks
385        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
386
387        // Launch the task
388        if (toTask.isActive) {
389            // Bring an active task to the foreground
390            ssp.moveTaskToFront(toTask.key.id, launchOpts);
391        } else {
392            ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
393        }
394    }
395
396    public void showNextAffiliatedTask() {
397        // Keep track of when the affiliated task is triggered
398        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
399        showRelativeAffiliatedTask(true);
400    }
401
402    public void showPrevAffiliatedTask() {
403        // Keep track of when the affiliated task is triggered
404        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
405        showRelativeAffiliatedTask(false);
406    }
407
408    public void dockTopTask() {
409        SystemServicesProxy ssp = Recents.getSystemServices();
410        ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
411        if (topTask != null && !ssp.isInHomeStack(topTask.id)) {
412            ssp.startTaskInDockedMode(topTask.id,
413                    ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
414            showRecents(false /* triggeredFromAltTab */);
415        }
416    }
417
418    /**
419     * Returns the preloaded load plan and invalidates it.
420     */
421    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
422        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
423        sInstanceLoadPlan = null;
424        return plan;
425    }
426
427    /**
428     * Prepares the header bar layout for the next transition, if the task view bounds has changed
429     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
430     *
431     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
432     *                               is not already bound (can be expensive)
433     */
434    private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
435        SystemServicesProxy ssp = Recents.getSystemServices();
436        Rect windowRect = ssp.getWindowRect();
437
438        // Update the configuration for the current state
439        mConfig.update(mContext, ssp, ssp.getWindowRect());
440
441        if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
442            // Try and pre-emptively bind the search widget on startup to ensure that we
443            // have the right thumbnail bounds to animate to.
444            // Note: We have to reload the widget id before we get the task stack bounds below
445            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
446                mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
447            }
448        }
449        Rect systemInsets = new Rect(0, mStatusBarHeight,
450                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
451                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
452        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
453                mSearchBarBounds, mTaskStackBounds);
454
455        // Rebind the header bar and draw it for the transition
456        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
457        Rect taskStackBounds = new Rect(mTaskStackBounds);
458        algo.setSystemInsets(systemInsets);
459        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
460        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
461        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
462            mLastTaskViewBounds.set(taskViewBounds);
463
464            int taskViewWidth = taskViewBounds.width();
465            synchronized (mHeaderBarLock) {
466                mHeaderBar.measure(
467                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
468                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
469                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
470            }
471        }
472    }
473
474    /**
475     * Preloads the icon of a task.
476     */
477    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
478        // Ensure that we load the running task's icon
479        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
480        launchOpts.runningTaskId = task.id;
481        launchOpts.loadThumbnails = false;
482        launchOpts.onlyLoadForCache = true;
483        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
484    }
485
486    /**
487     * Caches the header thumbnail used for a window animation asynchronously into
488     * {@link #mThumbnailTransitionBitmapCache}.
489     */
490    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
491            TaskStack stack, TaskStackView stackView) {
492        preloadIcon(topTask);
493
494        // Update the header bar if necessary
495        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
496
497        // Update the destination rect
498        mDummyStackView.updateMinMaxScrollForStack(stack);
499        final Task toTask = new Task();
500        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
501                topTask.id, toTask);
502        ForegroundThread.getHandler().post(new Runnable() {
503            @Override
504            public void run() {
505                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
506                mHandler.post(new Runnable() {
507                    @Override
508                    public void run() {
509                        mThumbnailTransitionBitmapCache = transitionBitmap;
510                        mThumbnailTransitionBitmapCacheKey = toTask;
511                    }
512                });
513            }
514        });
515    }
516
517    /**
518     * Creates the activity options for a unknown state->recents transition.
519     */
520    private ActivityOptions getUnknownTransitionActivityOptions() {
521        return ActivityOptions.makeCustomAnimation(mContext,
522                R.anim.recents_from_unknown_enter,
523                R.anim.recents_from_unknown_exit,
524                mHandler, this);
525    }
526
527    /**
528     * Creates the activity options for a home->recents transition.
529     */
530    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
531        if (fromSearchHome) {
532            return ActivityOptions.makeCustomAnimation(mContext,
533                    R.anim.recents_from_search_launcher_enter,
534                    R.anim.recents_from_search_launcher_exit,
535                    mHandler, this);
536        }
537        return ActivityOptions.makeCustomAnimation(mContext,
538                R.anim.recents_from_launcher_enter,
539                R.anim.recents_from_launcher_exit,
540                mHandler, this);
541    }
542
543    /**
544     * Creates the activity options for an app->recents transition.
545     */
546    private ActivityOptions getThumbnailTransitionActivityOptions(
547            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
548
549        // Update the destination rect
550        Task toTask = new Task();
551        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
552                topTask.id, toTask);
553        RectF toTaskRect = toTransform.rect;
554        Bitmap thumbnail;
555        if (mThumbnailTransitionBitmapCacheKey != null
556                && mThumbnailTransitionBitmapCacheKey.key != null
557                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
558            thumbnail = mThumbnailTransitionBitmapCache;
559            mThumbnailTransitionBitmapCacheKey = null;
560            mThumbnailTransitionBitmapCache = null;
561        } else {
562            preloadIcon(topTask);
563            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
564        }
565        if (thumbnail != null) {
566            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
567                    thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
568                    (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
569        }
570
571        // If both the screenshot and thumbnail fails, then just fall back to the default transition
572        return getUnknownTransitionActivityOptions();
573    }
574
575    /**
576     * Returns the transition rect for the given task id.
577     */
578    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
579            TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
580        // Find the running task in the TaskStack
581        Task task = null;
582        ArrayList<Task> tasks = stack.getTasks();
583        if (runningTaskId != -1) {
584            // Otherwise, try and find the task with the
585            int taskCount = tasks.size();
586            for (int i = taskCount - 1; i >= 0; i--) {
587                Task t = tasks.get(i);
588                if (t.key.id == runningTaskId) {
589                    task = t;
590                    runningTaskOut.copyFrom(t);
591                    break;
592                }
593            }
594        }
595        if (task == null) {
596            // If no task is specified or we can not find the task just use the front most one
597            task = tasks.get(tasks.size() - 1);
598            runningTaskOut.copyFrom(task);
599        }
600
601        // Get the transform for the running task
602        stackView.getScroller().setStackScrollToInitialState();
603        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
604                stackView.getScroller().getStackScroll(), mTmpTransform, null);
605        return mTmpTransform;
606    }
607
608    /**
609     * Draws the header of a task used for the window animation into a bitmap.
610     */
611    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
612        if (toTransform != null && toTask.key != null) {
613            Bitmap thumbnail;
614            synchronized (mHeaderBarLock) {
615                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
616                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
617                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
618                        Bitmap.Config.ARGB_8888);
619                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
620                    thumbnail.eraseColor(0xFFff0000);
621                } else {
622                    Canvas c = new Canvas(thumbnail);
623                    c.scale(toTransform.scale, toTransform.scale);
624                    mHeaderBar.rebindToTask(toTask);
625                    mHeaderBar.draw(c);
626                    c.setBitmap(null);
627                }
628            }
629            return thumbnail.createAshmemBitmap();
630        }
631        return null;
632    }
633
634    /**
635     * Shows the recents activity
636     */
637    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
638            boolean isTopTaskHome) {
639        SystemServicesProxy ssp = Recents.getSystemServices();
640        RecentsTaskLoader loader = Recents.getTaskLoader();
641
642        // Update the header bar if necessary
643        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
644
645        if (sInstanceLoadPlan == null) {
646            // Create a new load plan if onPreloadRecents() was never triggered
647            sInstanceLoadPlan = loader.createLoadPlan(mContext);
648        }
649
650        if (!sInstanceLoadPlan.hasTasks()) {
651            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
652        }
653        TaskStack stack = sInstanceLoadPlan.getTaskStack();
654
655        // Prepare the dummy stack for the transition
656        mDummyStackView.updateMinMaxScrollForStack(stack);
657        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
658                mDummyStackView.computeStackVisibilityReport();
659        boolean hasRecentTasks = stack.getTaskCount() > 0;
660        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
661
662        if (useThumbnailTransition) {
663            // Try starting with a thumbnail transition
664            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
665                    mDummyStackView);
666            if (opts != null) {
667                startRecentsActivity(topTask, opts, false /* fromHome */,
668                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
669            } else {
670                // Fall through below to the non-thumbnail transition
671                useThumbnailTransition = false;
672            }
673        }
674
675        if (!useThumbnailTransition) {
676            // If there is no thumbnail transition, but is launching from home into recents, then
677            // use a quick home transition and do the animation from home
678            if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
679                String homeActivityPackage = ssp.getHomeActivityPackageName();
680                String searchWidgetPackage = Prefs.getString(mContext,
681                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
682
683                // Determine whether we are coming from a search owned home activity
684                boolean fromSearchHome = (homeActivityPackage != null) &&
685                        homeActivityPackage.equals(searchWidgetPackage);
686                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
687                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
688                        false /* fromThumbnail */, stackVr);
689            } else {
690                // Otherwise we do the normal fade from an unknown source
691                ActivityOptions opts = getUnknownTransitionActivityOptions();
692                startRecentsActivity(topTask, opts, true /* fromHome */,
693                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
694            }
695        }
696        mLastToggleTime = SystemClock.elapsedRealtime();
697    }
698
699    /**
700     * Starts the recents activity.
701     */
702    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
703              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
704              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
705        mStartAnimationTriggered = false;
706
707        // Update the configuration based on the launch options
708        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
709        launchState.launchedFromHome = fromSearchHome || fromHome;
710        launchState.launchedFromSearchHome = fromSearchHome;
711        launchState.launchedFromAppWithThumbnail = fromThumbnail;
712        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
713        launchState.launchedWithAltTab = mTriggeredFromAltTab;
714        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
715        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
716        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
717        launchState.launchedHasConfigurationChanged = false;
718
719        Intent intent = new Intent();
720        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
721        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
722                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
723                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
724        if (opts != null) {
725            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
726        } else {
727            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
728        }
729        mCanReuseTaskStackViews = true;
730    }
731
732    /**** OnAnimationStartedListener Implementation ****/
733
734    @Override
735    public void onAnimationStarted() {
736        // Notify recents to start the enter animation
737        if (!mStartAnimationTriggered) {
738            mStartAnimationTriggered = true;
739            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
740        }
741    }
742}
743