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