RecentsImpl.java revision 2364f26dc24191e5bfbab45bc1bdf9babe13af80
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    /**
409     * Returns the preloaded load plan and invalidates it.
410     */
411    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
412        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
413        sInstanceLoadPlan = null;
414        return plan;
415    }
416
417    /**
418     * Prepares the header bar layout for the next transition, if the task view bounds has changed
419     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
420     *
421     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
422     *                               is not already bound (can be expensive)
423     */
424    private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
425        SystemServicesProxy ssp = Recents.getSystemServices();
426        Rect windowRect = ssp.getWindowRect();
427
428        // Update the configuration for the current state
429        mConfig.update(mContext, ssp, ssp.getWindowRect());
430
431        if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
432            // Try and pre-emptively bind the search widget on startup to ensure that we
433            // have the right thumbnail bounds to animate to.
434            // Note: We have to reload the widget id before we get the task stack bounds below
435            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
436                mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
437            }
438        }
439        Rect systemInsets = new Rect(0, mStatusBarHeight,
440                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
441                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
442        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
443                mSearchBarBounds, mTaskStackBounds);
444
445        // Rebind the header bar and draw it for the transition
446        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
447        Rect taskStackBounds = new Rect(mTaskStackBounds);
448        algo.setSystemInsets(systemInsets);
449        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
450        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
451        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
452            mLastTaskViewBounds.set(taskViewBounds);
453
454            int taskViewWidth = taskViewBounds.width();
455            synchronized (mHeaderBarLock) {
456                mHeaderBar.measure(
457                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
458                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
459                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
460            }
461        }
462    }
463
464    /**
465     * Preloads the icon of a task.
466     */
467    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
468        // Ensure that we load the running task's icon
469        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
470        launchOpts.runningTaskId = task.id;
471        launchOpts.loadThumbnails = false;
472        launchOpts.onlyLoadForCache = true;
473        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
474    }
475
476    /**
477     * Caches the header thumbnail used for a window animation asynchronously into
478     * {@link #mThumbnailTransitionBitmapCache}.
479     */
480    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
481            TaskStack stack, TaskStackView stackView) {
482        preloadIcon(topTask);
483
484        // Update the header bar if necessary
485        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
486
487        // Update the destination rect
488        mDummyStackView.updateMinMaxScrollForStack(stack);
489        final Task toTask = new Task();
490        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
491                topTask.id, toTask);
492        ForegroundThread.getHandler().post(new Runnable() {
493            @Override
494            public void run() {
495                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
496                mHandler.post(new Runnable() {
497                    @Override
498                    public void run() {
499                        mThumbnailTransitionBitmapCache = transitionBitmap;
500                        mThumbnailTransitionBitmapCacheKey = toTask;
501                    }
502                });
503            }
504        });
505    }
506
507    /**
508     * Creates the activity options for a unknown state->recents transition.
509     */
510    private ActivityOptions getUnknownTransitionActivityOptions() {
511        return ActivityOptions.makeCustomAnimation(mContext,
512                R.anim.recents_from_unknown_enter,
513                R.anim.recents_from_unknown_exit,
514                mHandler, this);
515    }
516
517    /**
518     * Creates the activity options for a home->recents transition.
519     */
520    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
521        if (fromSearchHome) {
522            return ActivityOptions.makeCustomAnimation(mContext,
523                    R.anim.recents_from_search_launcher_enter,
524                    R.anim.recents_from_search_launcher_exit,
525                    mHandler, this);
526        }
527        return ActivityOptions.makeCustomAnimation(mContext,
528                R.anim.recents_from_launcher_enter,
529                R.anim.recents_from_launcher_exit,
530                mHandler, this);
531    }
532
533    /**
534     * Creates the activity options for an app->recents transition.
535     */
536    private ActivityOptions getThumbnailTransitionActivityOptions(
537            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
538
539        // Update the destination rect
540        Task toTask = new Task();
541        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
542                topTask.id, toTask);
543        RectF toTaskRect = toTransform.rect;
544        Bitmap thumbnail;
545        if (mThumbnailTransitionBitmapCacheKey != null
546                && mThumbnailTransitionBitmapCacheKey.key != null
547                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
548            thumbnail = mThumbnailTransitionBitmapCache;
549            mThumbnailTransitionBitmapCacheKey = null;
550            mThumbnailTransitionBitmapCache = null;
551        } else {
552            preloadIcon(topTask);
553            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
554        }
555        if (thumbnail != null) {
556            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
557                    thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
558                    (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
559        }
560
561        // If both the screenshot and thumbnail fails, then just fall back to the default transition
562        return getUnknownTransitionActivityOptions();
563    }
564
565    /**
566     * Returns the transition rect for the given task id.
567     */
568    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
569            TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
570        // Find the running task in the TaskStack
571        Task task = null;
572        ArrayList<Task> tasks = stack.getTasks();
573        if (runningTaskId != -1) {
574            // Otherwise, try and find the task with the
575            int taskCount = tasks.size();
576            for (int i = taskCount - 1; i >= 0; i--) {
577                Task t = tasks.get(i);
578                if (t.key.id == runningTaskId) {
579                    task = t;
580                    runningTaskOut.copyFrom(t);
581                    break;
582                }
583            }
584        }
585        if (task == null) {
586            // If no task is specified or we can not find the task just use the front most one
587            task = tasks.get(tasks.size() - 1);
588            runningTaskOut.copyFrom(task);
589        }
590
591        // Get the transform for the running task
592        stackView.getScroller().setStackScrollToInitialState();
593        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
594                stackView.getScroller().getStackScroll(), mTmpTransform, null);
595        return mTmpTransform;
596    }
597
598    /**
599     * Draws the header of a task used for the window animation into a bitmap.
600     */
601    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
602        if (toTransform != null && toTask.key != null) {
603            Bitmap thumbnail;
604            synchronized (mHeaderBarLock) {
605                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
606                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
607                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
608                        Bitmap.Config.ARGB_8888);
609                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
610                    thumbnail.eraseColor(0xFFff0000);
611                } else {
612                    Canvas c = new Canvas(thumbnail);
613                    c.scale(toTransform.scale, toTransform.scale);
614                    mHeaderBar.rebindToTask(toTask);
615                    mHeaderBar.draw(c);
616                    c.setBitmap(null);
617                }
618            }
619            return thumbnail.createAshmemBitmap();
620        }
621        return null;
622    }
623
624    /**
625     * Shows the recents activity
626     */
627    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
628            boolean isTopTaskHome) {
629        SystemServicesProxy ssp = Recents.getSystemServices();
630        RecentsTaskLoader loader = Recents.getTaskLoader();
631
632        // Update the header bar if necessary
633        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
634
635        if (sInstanceLoadPlan == null) {
636            // Create a new load plan if onPreloadRecents() was never triggered
637            sInstanceLoadPlan = loader.createLoadPlan(mContext);
638        }
639
640        if (!sInstanceLoadPlan.hasTasks()) {
641            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
642        }
643        TaskStack stack = sInstanceLoadPlan.getTaskStack();
644
645        // Prepare the dummy stack for the transition
646        mDummyStackView.updateMinMaxScrollForStack(stack);
647        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
648                mDummyStackView.computeStackVisibilityReport();
649        boolean hasRecentTasks = stack.getTaskCount() > 0;
650        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
651
652        if (useThumbnailTransition) {
653            // Try starting with a thumbnail transition
654            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
655                    mDummyStackView);
656            if (opts != null) {
657                startRecentsActivity(topTask, opts, false /* fromHome */,
658                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
659            } else {
660                // Fall through below to the non-thumbnail transition
661                useThumbnailTransition = false;
662            }
663        }
664
665        if (!useThumbnailTransition) {
666            // If there is no thumbnail transition, but is launching from home into recents, then
667            // use a quick home transition and do the animation from home
668            if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
669                String homeActivityPackage = ssp.getHomeActivityPackageName();
670                String searchWidgetPackage = Prefs.getString(mContext,
671                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
672
673                // Determine whether we are coming from a search owned home activity
674                boolean fromSearchHome = (homeActivityPackage != null) &&
675                        homeActivityPackage.equals(searchWidgetPackage);
676                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
677                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
678                        false /* fromThumbnail */, stackVr);
679            } else {
680                // Otherwise we do the normal fade from an unknown source
681                ActivityOptions opts = getUnknownTransitionActivityOptions();
682                startRecentsActivity(topTask, opts, true /* fromHome */,
683                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
684            }
685        }
686        mLastToggleTime = SystemClock.elapsedRealtime();
687    }
688
689    /**
690     * Starts the recents activity.
691     */
692    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
693              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
694              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
695        mStartAnimationTriggered = false;
696
697        // Update the configuration based on the launch options
698        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
699        launchState.launchedFromHome = fromSearchHome || fromHome;
700        launchState.launchedFromSearchHome = fromSearchHome;
701        launchState.launchedFromAppWithThumbnail = fromThumbnail;
702        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
703        launchState.launchedWithAltTab = mTriggeredFromAltTab;
704        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
705        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
706        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
707        launchState.launchedHasConfigurationChanged = false;
708
709        Intent intent = new Intent();
710        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
711        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
712                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
713                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
714        if (opts != null) {
715            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
716        } else {
717            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
718        }
719        mCanReuseTaskStackViews = true;
720    }
721
722    /**** OnAnimationStartedListener Implementation ****/
723
724    @Override
725    public void onAnimationStarted() {
726        // Notify recents to start the enter animation
727        if (!mStartAnimationTriggered) {
728            mStartAnimationTriggered = true;
729            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
730        }
731    }
732}
733