AlternateRecentsComponent.java revision 740c3ac782675d190941b2ab1905e56f246c1b11
1/*
2 * Copyright (C) 2014 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.appwidget.AppWidgetHost;
24import android.appwidget.AppWidgetProviderInfo;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.graphics.Bitmap;
33import android.graphics.Canvas;
34import android.graphics.Rect;
35import android.os.Handler;
36import android.os.SystemClock;
37import android.os.UserHandle;
38import android.util.Pair;
39import android.view.LayoutInflater;
40import android.view.View;
41
42import com.android.systemui.R;
43import com.android.systemui.RecentsComponent;
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;
55
56import java.util.ArrayList;
57import java.util.List;
58import java.util.concurrent.atomic.AtomicBoolean;
59
60
61/** A proxy implementation for the recents component */
62public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
63
64    final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
65    final public static String EXTRA_FROM_SEARCH_HOME = "recents.triggeredOverSearchHome";
66    final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
67    final public static String EXTRA_FROM_TASK_ID = "recents.activeTaskId";
68    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
69    final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
70    final public static String EXTRA_REUSE_TASK_STACK_VIEWS = "recents.reuseTaskStackViews";
71    final public static String EXTRA_NUM_VISIBLE_TASKS = "recents.numVisibleTasks";
72    final public static String EXTRA_NUM_VISIBLE_THUMBNAILS = "recents.numVisibleThumbnails";
73
74    final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
75    final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
76    final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
77
78    final static int sMinToggleDelay = 350;
79
80    final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
81    final static String sRecentsPackage = "com.android.systemui";
82    final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
83
84    /**
85     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
86     * task stacks and update recents accordingly.
87     */
88    class TaskStackListenerImpl extends ITaskStackListener.Stub {
89        @Override
90        public void onTaskStackChanged() {
91            RecentsConfiguration config = RecentsConfiguration.getInstance();
92            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
93                // Load the next task only if we aren't svelte
94                RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
95                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
96                loader.preloadTasks(plan, true /* isTopTaskHome */);
97                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
98                launchOpts.numVisibleTasks = 1;
99                launchOpts.numVisibleTaskThumbnails = 1;
100                launchOpts.onlyLoadForCache = true;
101                loader.loadTasks(mContext, plan, launchOpts);
102            }
103        }
104    }
105
106    static RecentsComponent.Callbacks sRecentsComponentCallbacks;
107    static RecentsTaskLoadPlan sInstanceLoadPlan;
108
109    Context mContext;
110    LayoutInflater mInflater;
111    SystemServicesProxy mSystemServicesProxy;
112    Handler mHandler;
113    TaskStackListenerImpl mTaskStackListener;
114    boolean mBootCompleted;
115    boolean mStartAnimationTriggered;
116    boolean mCanReuseTaskStackViews = true;
117
118    // Task launching
119    RecentsConfiguration mConfig;
120    Rect mWindowRect = new Rect();
121    Rect mTaskStackBounds = new Rect();
122    Rect mSystemInsets = new Rect();
123    TaskViewTransform mTmpTransform = new TaskViewTransform();
124    int mStatusBarHeight;
125    int mNavBarHeight;
126    int mNavBarWidth;
127
128    // Header (for transition)
129    TaskViewHeader mHeaderBar;
130    TaskStackView mDummyStackView;
131
132    // Variables to keep track of if we need to start recents after binding
133    View mStatusBarView;
134    boolean mTriggeredFromAltTab;
135    long mLastToggleTime;
136
137    public AlternateRecentsComponent(Context context) {
138        RecentsTaskLoader.initialize(context);
139        mInflater = LayoutInflater.from(context);
140        mContext = context;
141        mSystemServicesProxy = new SystemServicesProxy(context);
142        mHandler = new Handler();
143        mTaskStackBounds = new Rect();
144
145        // Register the task stack listener
146        mTaskStackListener = new TaskStackListenerImpl();
147        mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
148    }
149
150    public void onStart() {
151        // Initialize some static datastructures
152        TaskStackViewLayoutAlgorithm.initializeCurve();
153        // Load the header bar layout
154        reloadHeaderBarLayout();
155        // Try and pre-emptively bind the search widget on startup to ensure that we
156        // have the right thumbnail bounds to animate to.
157        if (Constants.DebugFlags.App.EnableSearchLayout) {
158            // If there is no id, then bind a new search app widget
159            if (mConfig.searchBarAppWidgetId < 0) {
160                AppWidgetHost host = new RecentsAppWidgetHost(mContext,
161                        Constants.Values.App.AppWidgetHostId);
162                Pair<Integer, AppWidgetProviderInfo> widgetInfo =
163                        mSystemServicesProxy.bindSearchAppWidget(host);
164                if (widgetInfo != null) {
165                    // Save the app widget id into the settings
166                    mConfig.updateSearchBarAppWidgetId(mContext, widgetInfo.first);
167                }
168            }
169        }
170
171        // When we start, preload the data associated with the previous recent tasks.
172        // We can use a new plan since the caches will be the same.
173        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
174        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
175        loader.preloadTasks(plan, true /* isTopTaskHome */);
176        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
177        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
178        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
179        launchOpts.onlyLoadForCache = true;
180        loader.loadTasks(mContext, plan, launchOpts);
181    }
182
183    public void onBootCompleted() {
184        mBootCompleted = true;
185    }
186
187    /** Shows the recents */
188    public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
189        mStatusBarView = statusBarView;
190        mTriggeredFromAltTab = triggeredFromAltTab;
191
192        try {
193            startRecentsActivity();
194        } catch (ActivityNotFoundException e) {
195            Console.logRawError("Failed to launch RecentAppsIntent", e);
196        }
197    }
198
199    /** Hides the recents */
200    public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
201        if (mBootCompleted) {
202            ActivityManager.RunningTaskInfo topTask = getTopMostTask();
203            if (topTask != null && isRecentsTopMost(topTask, null)) {
204                // Notify recents to hide itself
205                Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY);
206                intent.setPackage(mContext.getPackageName());
207                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
208                        Intent.FLAG_RECEIVER_FOREGROUND);
209                intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
210                intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
211                mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
212            }
213        }
214    }
215
216    /** Toggles the alternate recents activity */
217    public void onToggleRecents(View statusBarView) {
218        mStatusBarView = statusBarView;
219        mTriggeredFromAltTab = false;
220
221        try {
222            toggleRecentsActivity();
223        } catch (ActivityNotFoundException e) {
224            Console.logRawError("Failed to launch RecentAppsIntent", e);
225        }
226    }
227
228    public void onPreloadRecents() {
229        // Preload only the raw task list into a new load plan (which will be consumed by the
230        // RecentsActivity)
231        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
232        sInstanceLoadPlan = loader.createLoadPlan(mContext);
233        sInstanceLoadPlan.preloadRawTasks(true);
234    }
235
236    public void onCancelPreloadingRecents() {
237        // Do nothing
238    }
239
240    void showRelativeAffiliatedTask(boolean showNextTask) {
241        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
242        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
243        loader.preloadTasks(plan, true /* isTopTaskHome */);
244        TaskStack stack = plan.getTaskStack();
245
246        // Return early if there are no tasks
247        if (stack.getTaskCount() == 0) return;
248
249        ActivityManager.RunningTaskInfo runningTask = getTopMostTask();
250        // Return early if there is no running task (can't determine affiliated tasks in this case)
251        if (runningTask == null) return;
252        // Return early if the running task is in the home stack (optimization)
253        if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
254
255        // Find the task in the recents list
256        ArrayList<Task> tasks = stack.getTasks();
257        Task toTask = null;
258        ActivityOptions launchOpts = null;
259        int taskCount = tasks.size();
260        int numAffiliatedTasks = 0;
261        for (int i = 0; i < taskCount; i++) {
262            Task task = tasks.get(i);
263            if (task.key.id == runningTask.id) {
264                TaskGrouping group = task.group;
265                Task.TaskKey toTaskKey;
266                if (showNextTask) {
267                    toTaskKey = group.getNextTaskInGroup(task);
268                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
269                            R.anim.recents_launch_next_affiliated_task_target,
270                            R.anim.recents_launch_next_affiliated_task_source);
271                } else {
272                    toTaskKey = group.getPrevTaskInGroup(task);
273                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
274                            R.anim.recents_launch_prev_affiliated_task_target,
275                            R.anim.recents_launch_prev_affiliated_task_source);
276                }
277                if (toTaskKey != null) {
278                    toTask = stack.findTaskWithId(toTaskKey.id);
279                }
280                numAffiliatedTasks = group.getTaskCount();
281                break;
282            }
283        }
284
285        // Return early if there is no next task
286        if (toTask == null) {
287            if (numAffiliatedTasks > 1) {
288                if (showNextTask) {
289                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
290                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
291                                    R.anim.recents_launch_next_affiliated_task_bounce));
292                } else {
293                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
294                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
295                                    R.anim.recents_launch_prev_affiliated_task_bounce));
296                }
297            }
298            return;
299        }
300
301        // Launch the task
302        if (toTask.isActive) {
303            // Bring an active task to the foreground
304            mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
305        } else {
306            mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
307                    toTask.activityLabel, launchOpts);
308        }
309    }
310
311    public void onShowNextAffiliatedTask() {
312        showRelativeAffiliatedTask(true);
313    }
314
315    public void onShowPrevAffiliatedTask() {
316        showRelativeAffiliatedTask(false);
317    }
318
319    public void onConfigurationChanged(Configuration newConfig) {
320        // Don't reuse task stack views if the configuration changes
321        mCanReuseTaskStackViews = false;
322        // Reload the header bar layout
323        reloadHeaderBarLayout();
324    }
325
326    /** Prepares the header bar layout. */
327    void reloadHeaderBarLayout() {
328        Resources res = mContext.getResources();
329        mWindowRect = mSystemServicesProxy.getWindowRect();
330        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
331        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
332        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
333        mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
334        mConfig.updateOnConfigurationChange();
335        mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
336                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds);
337        if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
338            mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
339        } else {
340            mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
341        }
342
343        // Inflate the header bar layout so that we can rebind and draw it for the transition
344        TaskStack stack = new TaskStack();
345        mDummyStackView = new TaskStackView(mContext, stack);
346        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
347        Rect taskStackBounds = new Rect(mTaskStackBounds);
348        taskStackBounds.bottom -= mSystemInsets.bottom;
349        algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
350        Rect taskViewSize = algo.getUntransformedTaskViewSize();
351        int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
352        mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
353                false);
354        mHeaderBar.measure(
355                View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
356                View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
357        mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
358    }
359
360    /** Gets the top task. */
361    ActivityManager.RunningTaskInfo getTopMostTask() {
362        SystemServicesProxy ssp = mSystemServicesProxy;
363        List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
364        if (!tasks.isEmpty()) {
365            return tasks.get(0);
366        }
367        return null;
368    }
369
370    /** Returns whether the recents is currently running */
371    boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) {
372        SystemServicesProxy ssp = mSystemServicesProxy;
373        if (topTask != null) {
374            ComponentName topActivity = topTask.topActivity;
375
376            // Check if the front most activity is recents
377            if (topActivity.getPackageName().equals(sRecentsPackage) &&
378                    topActivity.getClassName().equals(sRecentsActivity)) {
379                if (isHomeTopMost != null) {
380                    isHomeTopMost.set(false);
381                }
382                return true;
383            }
384
385            if (isHomeTopMost != null) {
386                isHomeTopMost.set(ssp.isInHomeStack(topTask.id));
387            }
388        }
389        return false;
390    }
391
392    /** Toggles the recents activity */
393    void toggleRecentsActivity() {
394        // If the user has toggled it too quickly, then just eat up the event here (it's better than
395        // showing a janky screenshot).
396        // NOTE: Ideally, the screenshot mechanism would take the window transform into account
397        if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
398            return;
399        }
400
401        // If Recents is the front most activity, then we should just communicate with it directly
402        // to launch the first task or dismiss itself
403        ActivityManager.RunningTaskInfo topTask = getTopMostTask();
404        AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
405        if (topTask != null && isRecentsTopMost(topTask, isTopTaskHome)) {
406            // Notify recents to toggle itself
407            Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
408            intent.setPackage(mContext.getPackageName());
409            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
410                    Intent.FLAG_RECEIVER_FOREGROUND);
411            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
412            mLastToggleTime = SystemClock.elapsedRealtime();
413            return;
414        } else {
415            // Otherwise, start the recents activity
416            startRecentsActivity(topTask, isTopTaskHome.get());
417        }
418    }
419
420    /** Starts the recents activity if it is not already running */
421    void startRecentsActivity() {
422        // Check if the top task is in the home stack, and start the recents activity
423        ActivityManager.RunningTaskInfo topTask = getTopMostTask();
424        AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
425        if (topTask == null || !isRecentsTopMost(topTask, isTopTaskHome)) {
426            startRecentsActivity(topTask, isTopTaskHome.get());
427        }
428    }
429
430    /**
431     * Creates the activity options for a unknown state->recents transition.
432     */
433    ActivityOptions getUnknownTransitionActivityOptions() {
434        mStartAnimationTriggered = false;
435        return ActivityOptions.makeCustomAnimation(mContext,
436                R.anim.recents_from_unknown_enter,
437                R.anim.recents_from_unknown_exit,
438                mHandler, this);
439    }
440
441    /**
442     * Creates the activity options for a home->recents transition.
443     */
444    ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
445        mStartAnimationTriggered = false;
446        if (fromSearchHome) {
447            return ActivityOptions.makeCustomAnimation(mContext,
448                    R.anim.recents_from_search_launcher_enter,
449                    R.anim.recents_from_search_launcher_exit,
450                    mHandler, this);
451        }
452        return ActivityOptions.makeCustomAnimation(mContext,
453                R.anim.recents_from_launcher_enter,
454                R.anim.recents_from_launcher_exit,
455                mHandler, this);
456    }
457
458    /**
459     * Creates the activity options for an app->recents transition.
460     */
461    ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
462            TaskStack stack, TaskStackView stackView) {
463        // Update the destination rect
464        Task toTask = new Task();
465        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
466                topTask.id, toTask);
467        if (toTransform != null && toTask.key != null) {
468            Rect toTaskRect = toTransform.rect;
469            int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
470            int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
471            Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
472                    Bitmap.Config.ARGB_8888);
473            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
474                thumbnail.eraseColor(0xFFff0000);
475            } else {
476                Canvas c = new Canvas(thumbnail);
477                c.scale(toTransform.scale, toTransform.scale);
478                mHeaderBar.rebindToTask(toTask);
479                mHeaderBar.draw(c);
480                c.setBitmap(null);
481            }
482
483            mStartAnimationTriggered = false;
484            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView,
485                    thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
486                    toTaskRect.height(), this);
487        }
488
489        // If both the screenshot and thumbnail fails, then just fall back to the default transition
490        return getUnknownTransitionActivityOptions();
491    }
492
493    /** Returns the transition rect for the given task id. */
494    TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
495            int runningTaskId, Task runningTaskOut) {
496        // Find the running task in the TaskStack
497        Task task = null;
498        ArrayList<Task> tasks = stack.getTasks();
499        if (runningTaskId != -1) {
500            // Otherwise, try and find the task with the
501            int taskCount = tasks.size();
502            for (int i = taskCount - 1; i >= 0; i--) {
503                Task t = tasks.get(i);
504                if (t.key.id == runningTaskId) {
505                    task = t;
506                    runningTaskOut.copyFrom(t);
507                    break;
508                }
509            }
510        }
511        if (task == null) {
512            // If no task is specified or we can not find the task just use the front most one
513            task = tasks.get(tasks.size() - 1);
514        }
515
516        // Get the transform for the running task
517        stackView.getScroller().setStackScrollToInitialState();
518        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
519                stackView.getScroller().getStackScroll(), mTmpTransform, null);
520        return mTmpTransform;
521    }
522
523    /** Starts the recents activity */
524    void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
525        if (sInstanceLoadPlan == null) {
526            // Create a new load plan if onPreloadRecents() was never triggered
527            RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
528            sInstanceLoadPlan = loader.createLoadPlan(mContext);
529        }
530        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
531        loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
532        TaskStack stack = sInstanceLoadPlan.getTaskStack();
533
534        // Prepare the dummy stack for the transition
535        mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
536        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
537                mDummyStackView.computeStackVisibilityReport();
538        boolean hasRecentTasks = stack.getTaskCount() > 0;
539        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
540
541        if (useThumbnailTransition) {
542            // Ensure that we load the running task's icon
543            RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
544            launchOpts.runningTaskId = topTask.id;
545            launchOpts.loadThumbnails = false;
546            launchOpts.onlyLoadForCache = true;
547            loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts);
548
549            // Try starting with a thumbnail transition
550            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
551                    mDummyStackView);
552            if (opts != null) {
553                startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr);
554            } else {
555                // Fall through below to the non-thumbnail transition
556                useThumbnailTransition = false;
557            }
558        }
559
560        if (!useThumbnailTransition) {
561            // If there is no thumbnail transition, but is launching from home into recents, then
562            // use a quick home transition and do the animation from home
563            if (hasRecentTasks) {
564                // Get the home activity info
565                String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
566                // Get the search widget info
567                AppWidgetProviderInfo searchWidget = null;
568                String searchWidgetPackage = null;
569                if (mConfig.hasSearchBarAppWidget()) {
570                    searchWidget = mSystemServicesProxy.getAppWidgetInfo(
571                            mConfig.searchBarAppWidgetId);
572                } else {
573                    searchWidget = mSystemServicesProxy.resolveSearchAppWidget();
574                }
575                if (searchWidget != null && searchWidget.provider != null) {
576                    searchWidgetPackage = searchWidget.provider.getPackageName();
577                }
578                // Determine whether we are coming from a search owned home activity
579                boolean fromSearchHome = false;
580                if (homeActivityPackage != null && searchWidgetPackage != null &&
581                        homeActivityPackage.equals(searchWidgetPackage)) {
582                    fromSearchHome = true;
583                }
584
585                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
586                startAlternateRecentsActivity(topTask, opts,
587                        fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr);
588            } else {
589                // Otherwise we do the normal fade from an unknown source
590                ActivityOptions opts = getUnknownTransitionActivityOptions();
591                startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr);
592            }
593        }
594        mLastToggleTime = SystemClock.elapsedRealtime();
595    }
596
597    /** Starts the recents activity */
598    void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
599            ActivityOptions opts, String extraFlag,
600            TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
601        Intent intent = new Intent(sToggleRecentsAction);
602        intent.setClassName(sRecentsPackage, sRecentsActivity);
603        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
604                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
605                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
606        if (extraFlag != null) {
607            intent.putExtra(extraFlag, true);
608        }
609        intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
610        intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
611        intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews);
612        intent.putExtra(EXTRA_NUM_VISIBLE_TASKS, vr.numVisibleTasks);
613        intent.putExtra(EXTRA_NUM_VISIBLE_THUMBNAILS, vr.numVisibleThumbnails);
614        if (opts != null) {
615            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
616        } else {
617            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
618        }
619        mCanReuseTaskStackViews = true;
620    }
621
622    /** Sets the RecentsComponent callbacks. */
623    public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) {
624        sRecentsComponentCallbacks = cb;
625    }
626
627    /** Notifies the callbacks that the visibility of Recents has changed. */
628    public static void notifyVisibilityChanged(boolean visible) {
629        if (sRecentsComponentCallbacks != null) {
630            sRecentsComponentCallbacks.onVisibilityChanged(visible);
631        }
632    }
633
634    /**
635     * Returns the preloaded load plan and invalidates it.
636     */
637    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
638        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
639        sInstanceLoadPlan = null;
640        return plan;
641    }
642
643    /**** OnAnimationStartedListener Implementation ****/
644
645    @Override
646    public void onAnimationStarted() {
647        // Notify recents to start the enter animation
648        if (!mStartAnimationTriggered) {
649            // There can be a race condition between the start animation callback and
650            // the start of the new activity (where we register the receiver that listens
651            // to this broadcast, so we add our own receiver and if that gets called, then
652            // we know the activity has not yet started and we can retry sending the broadcast.
653            BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
654                @Override
655                public void onReceive(Context context, Intent intent) {
656                    if (getResultCode() == Activity.RESULT_OK) {
657                        mStartAnimationTriggered = true;
658                        return;
659                    }
660
661                    // Schedule for the broadcast to be sent again after some time
662                    mHandler.postDelayed(new Runnable() {
663                        @Override
664                        public void run() {
665                            onAnimationStarted();
666                        }
667                    }, 25);
668                }
669            };
670
671            // Send the broadcast to notify Recents that the animation has started
672            Intent intent = new Intent(ACTION_START_ENTER_ANIMATION);
673            intent.setPackage(mContext.getPackageName());
674            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
675                    Intent.FLAG_RECEIVER_FOREGROUND);
676            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
677                    fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
678        }
679    }
680}
681