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