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