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