Home | History | Annotate | Download | only in recents
      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 
     17 package com.android.systemui.recents;
     18 
     19 import android.app.Activity;
     20 import android.app.ActivityManager;
     21 import android.app.ActivityOptions;
     22 import android.appwidget.AppWidgetHost;
     23 import android.appwidget.AppWidgetProviderInfo;
     24 import android.content.ActivityNotFoundException;
     25 import android.content.BroadcastReceiver;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.res.Configuration;
     30 import android.content.res.Resources;
     31 import android.graphics.Bitmap;
     32 import android.graphics.Canvas;
     33 import android.graphics.Rect;
     34 import android.os.Handler;
     35 import android.os.UserHandle;
     36 import android.util.Pair;
     37 import android.view.LayoutInflater;
     38 import android.view.View;
     39 import com.android.systemui.R;
     40 import com.android.systemui.RecentsComponent;
     41 import com.android.systemui.recents.misc.Console;
     42 import com.android.systemui.recents.misc.SystemServicesProxy;
     43 import com.android.systemui.recents.model.RecentsTaskLoader;
     44 import com.android.systemui.recents.model.Task;
     45 import com.android.systemui.recents.model.TaskGrouping;
     46 import com.android.systemui.recents.model.TaskStack;
     47 import com.android.systemui.recents.views.TaskStackView;
     48 import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
     49 import com.android.systemui.recents.views.TaskViewHeader;
     50 import com.android.systemui.recents.views.TaskViewTransform;
     51 
     52 import java.util.ArrayList;
     53 import java.util.List;
     54 import java.util.concurrent.atomic.AtomicBoolean;
     55 
     56 /** A proxy implementation for the recents component */
     57 public 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, true, 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, isTopTaskHome, 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(), isTopTaskHome);
    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