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