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