Home | History | Annotate | Download | only in recents
      1 /*
      2  * Copyright (C) 2015 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 static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
     20 import static android.view.View.MeasureSpec;
     21 
     22 import android.app.ActivityManager;
     23 import android.app.ActivityOptions;
     24 import android.content.ActivityNotFoundException;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.res.Resources;
     28 import android.graphics.Bitmap;
     29 import android.graphics.Canvas;
     30 import android.graphics.Rect;
     31 import android.graphics.RectF;
     32 import android.graphics.drawable.Drawable;
     33 import android.os.Handler;
     34 import android.os.SystemClock;
     35 import android.os.UserHandle;
     36 import android.util.Log;
     37 import android.util.MutableBoolean;
     38 import android.view.AppTransitionAnimationSpec;
     39 import android.view.LayoutInflater;
     40 import android.view.ViewConfiguration;
     41 import android.view.WindowManager;
     42 
     43 import com.android.internal.logging.MetricsLogger;
     44 import com.android.internal.policy.DockedDividerUtils;
     45 import com.android.systemui.R;
     46 import com.android.systemui.SystemUIApplication;
     47 import com.android.systemui.recents.events.EventBus;
     48 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
     49 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
     50 import com.android.systemui.recents.events.activity.HideRecentsEvent;
     51 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
     52 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
     53 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
     54 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
     55 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
     56 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
     57 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
     58 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
     59 import com.android.systemui.recents.misc.DozeTrigger;
     60 import com.android.systemui.recents.misc.ForegroundThread;
     61 import com.android.systemui.recents.misc.SystemServicesProxy;
     62 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
     63 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
     64 import com.android.systemui.recents.model.RecentsTaskLoader;
     65 import com.android.systemui.recents.model.Task;
     66 import com.android.systemui.recents.model.TaskGrouping;
     67 import com.android.systemui.recents.model.TaskStack;
     68 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
     69 import com.android.systemui.recents.views.TaskStackView;
     70 import com.android.systemui.recents.views.TaskStackViewScroller;
     71 import com.android.systemui.recents.views.TaskViewHeader;
     72 import com.android.systemui.recents.views.TaskViewTransform;
     73 import com.android.systemui.stackdivider.DividerView;
     74 import com.android.systemui.statusbar.BaseStatusBar;
     75 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
     76 import com.android.systemui.statusbar.phone.PhoneStatusBar;
     77 
     78 import java.util.ArrayList;
     79 
     80 /**
     81  * An implementation of the Recents component for the current user.  For secondary users, this can
     82  * be called remotely from the system user.
     83  */
     84 public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
     85 
     86     private final static String TAG = "RecentsImpl";
     87 
     88     // The minimum amount of time between each recents button press that we will handle
     89     private final static int MIN_TOGGLE_DELAY_MS = 350;
     90 
     91     // The duration within which the user releasing the alt tab (from when they pressed alt tab)
     92     // that the fast alt-tab animation will run.  If the user's alt-tab takes longer than this
     93     // duration, then we will toggle recents after this duration.
     94     private final static int FAST_ALT_TAB_DELAY_MS = 225;
     95 
     96     public final static String RECENTS_PACKAGE = "com.android.systemui";
     97     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
     98 
     99     /**
    100      * An implementation of TaskStackListener, that allows us to listen for changes to the system
    101      * task stacks and update recents accordingly.
    102      */
    103     class TaskStackListenerImpl extends TaskStackListener {
    104         @Override
    105         public void onTaskStackChanged() {
    106             // Preloads the next task
    107             RecentsConfiguration config = Recents.getConfiguration();
    108             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
    109                 RecentsTaskLoader loader = Recents.getTaskLoader();
    110                 SystemServicesProxy ssp = Recents.getSystemServices();
    111                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
    112 
    113                 // Load the next task only if we aren't svelte
    114                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
    115                 loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
    116                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
    117                 // This callback is made when a new activity is launched and the old one is paused
    118                 // so ignore the current activity and try and preload the thumbnail for the
    119                 // previous one.
    120                 if (runningTaskInfo != null) {
    121                     launchOpts.runningTaskId = runningTaskInfo.id;
    122                 }
    123                 launchOpts.numVisibleTasks = 2;
    124                 launchOpts.numVisibleTaskThumbnails = 2;
    125                 launchOpts.onlyLoadForCache = true;
    126                 launchOpts.onlyLoadPausedActivities = true;
    127                 loader.loadTasks(mContext, plan, launchOpts);
    128             }
    129         }
    130     }
    131 
    132     protected static RecentsTaskLoadPlan sInstanceLoadPlan;
    133 
    134     protected Context mContext;
    135     protected Handler mHandler;
    136     TaskStackListenerImpl mTaskStackListener;
    137     boolean mDraggingInRecents;
    138     boolean mLaunchedWhileDocking;
    139 
    140     // Task launching
    141     Rect mTaskStackBounds = new Rect();
    142     TaskViewTransform mTmpTransform = new TaskViewTransform();
    143     int mStatusBarHeight;
    144     int mNavBarHeight;
    145     int mNavBarWidth;
    146     int mTaskBarHeight;
    147 
    148     // Header (for transition)
    149     TaskViewHeader mHeaderBar;
    150     final Object mHeaderBarLock = new Object();
    151     protected TaskStackView mDummyStackView;
    152 
    153     // Variables to keep track of if we need to start recents after binding
    154     protected boolean mTriggeredFromAltTab;
    155     protected long mLastToggleTime;
    156     DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
    157         @Override
    158         public void run() {
    159             // When this fires, then the user has not released alt-tab for at least
    160             // FAST_ALT_TAB_DELAY_MS milliseconds
    161             showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
    162                     false /* reloadTasks */, false /* fromHome */,
    163                     DividerView.INVALID_RECENTS_GROW_TARGET);
    164         }
    165     });
    166 
    167     protected Bitmap mThumbTransitionBitmapCache;
    168 
    169     public RecentsImpl(Context context) {
    170         mContext = context;
    171         mHandler = new Handler();
    172 
    173         // Initialize the static foreground thread
    174         ForegroundThread.get();
    175 
    176         // Register the task stack listener
    177         mTaskStackListener = new TaskStackListenerImpl();
    178         SystemServicesProxy ssp = Recents.getSystemServices();
    179         ssp.registerTaskStackListener(mTaskStackListener);
    180 
    181         // Initialize the static configuration resources
    182         LayoutInflater inflater = LayoutInflater.from(mContext);
    183         mDummyStackView = new TaskStackView(mContext);
    184         mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
    185                 null, false);
    186         reloadResources();
    187     }
    188 
    189     public void onBootCompleted() {
    190         // When we start, preload the data associated with the previous recent tasks.
    191         // We can use a new plan since the caches will be the same.
    192         RecentsTaskLoader loader = Recents.getTaskLoader();
    193         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
    194         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
    195         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
    196         launchOpts.numVisibleTasks = loader.getIconCacheSize();
    197         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
    198         launchOpts.onlyLoadForCache = true;
    199         loader.loadTasks(mContext, plan, launchOpts);
    200     }
    201 
    202     public void onConfigurationChanged() {
    203         reloadResources();
    204         mDummyStackView.reloadOnConfigurationChange();
    205         mHeaderBar.onConfigurationChanged();
    206     }
    207 
    208     /**
    209      * This is only called from the system user's Recents.  Secondary users will instead proxy their
    210      * visibility change events through to the system user via
    211      * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
    212      */
    213     public void onVisibilityChanged(Context context, boolean visible) {
    214         SystemUIApplication app = (SystemUIApplication) context;
    215         PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
    216         if (statusBar != null) {
    217             statusBar.updateRecentsVisibility(visible);
    218         }
    219     }
    220 
    221     /**
    222      * This is only called from the system user's Recents.  Secondary users will instead proxy their
    223      * visibility change events through to the system user via
    224      * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
    225      */
    226     public void onStartScreenPinning(Context context, int taskId) {
    227         SystemUIApplication app = (SystemUIApplication) context;
    228         PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
    229         if (statusBar != null) {
    230             statusBar.showScreenPinningRequest(taskId, false);
    231         }
    232     }
    233 
    234     public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
    235             boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
    236             int growTarget) {
    237         mTriggeredFromAltTab = triggeredFromAltTab;
    238         mDraggingInRecents = draggingInRecents;
    239         mLaunchedWhileDocking = launchedWhileDockingTask;
    240         if (mFastAltTabTrigger.isAsleep()) {
    241             // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
    242             mFastAltTabTrigger.stopDozing();
    243         } else if (mFastAltTabTrigger.isDozing()) {
    244             // Fast alt-tab duration has not elapsed.  If this is triggered by a different
    245             // showRecents() call, then ignore that call for now.
    246             // TODO: We can not handle quick tabs that happen between the initial showRecents() call
    247             //       that started the activity and the activity starting up.  The severity of this
    248             //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
    249             if (!triggeredFromAltTab) {
    250                 return;
    251             }
    252             mFastAltTabTrigger.stopDozing();
    253         } else if (triggeredFromAltTab) {
    254             // The fast alt-tab detector is not yet running, so start the trigger and wait for the
    255             // hideRecents() call, or for the fast alt-tab duration to elapse
    256             mFastAltTabTrigger.startDozing();
    257             return;
    258         }
    259 
    260         try {
    261             // Check if the top task is in the home stack, and start the recents activity
    262             SystemServicesProxy ssp = Recents.getSystemServices();
    263             boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
    264             MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
    265             if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
    266                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
    267                 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
    268                         growTarget);
    269             }
    270         } catch (ActivityNotFoundException e) {
    271             Log.e(TAG, "Failed to launch RecentsActivity", e);
    272         }
    273     }
    274 
    275     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
    276         if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
    277             // The user has released alt-tab before the trigger has run, so just show the next
    278             // task immediately
    279             showNextTask();
    280 
    281             // Cancel the fast alt-tab trigger
    282             mFastAltTabTrigger.stopDozing();
    283             return;
    284         }
    285 
    286         // Defer to the activity to handle hiding recents, if it handles it, then it must still
    287         // be visible
    288         EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
    289                 triggeredFromHomeKey));
    290     }
    291 
    292     public void toggleRecents(int growTarget) {
    293         // Skip this toggle if we are already waiting to trigger recents via alt-tab
    294         if (mFastAltTabTrigger.isDozing()) {
    295             return;
    296         }
    297 
    298         mDraggingInRecents = false;
    299         mLaunchedWhileDocking = false;
    300         mTriggeredFromAltTab = false;
    301 
    302         try {
    303             SystemServicesProxy ssp = Recents.getSystemServices();
    304             MutableBoolean isHomeStackVisible = new MutableBoolean(true);
    305             long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
    306 
    307             if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
    308                 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
    309                 RecentsConfiguration config = Recents.getConfiguration();
    310                 RecentsActivityLaunchState launchState = config.getLaunchState();
    311                 if (!launchState.launchedWithAltTab) {
    312                     // If the user taps quickly
    313                     if (!debugFlags.isPagingEnabled() ||
    314                             (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
    315                                     elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
    316                         // Launch the next focused task
    317                         EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
    318                     } else {
    319                         // Notify recents to move onto the next task
    320                         EventBus.getDefault().post(new IterateRecentsEvent());
    321                     }
    322                 } else {
    323                     // If the user has toggled it too quickly, then just eat up the event here (it's
    324                     // better than showing a janky screenshot).
    325                     // NOTE: Ideally, the screenshot mechanism would take the window transform into
    326                     // account
    327                     if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
    328                         return;
    329                     }
    330 
    331                     EventBus.getDefault().post(new ToggleRecentsEvent());
    332                     mLastToggleTime = SystemClock.elapsedRealtime();
    333                 }
    334                 return;
    335             } else {
    336                 // If the user has toggled it too quickly, then just eat up the event here (it's
    337                 // better than showing a janky screenshot).
    338                 // NOTE: Ideally, the screenshot mechanism would take the window transform into
    339                 // account
    340                 if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
    341                     return;
    342                 }
    343 
    344                 // Otherwise, start the recents activity
    345                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
    346                 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
    347                         growTarget);
    348 
    349                 // Only close the other system windows if we are actually showing recents
    350                 ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
    351                 mLastToggleTime = SystemClock.elapsedRealtime();
    352             }
    353         } catch (ActivityNotFoundException e) {
    354             Log.e(TAG, "Failed to launch RecentsActivity", e);
    355         }
    356     }
    357 
    358     public void preloadRecents() {
    359         // Preload only the raw task list into a new load plan (which will be consumed by the
    360         // RecentsActivity) only if there is a task to animate to.
    361         SystemServicesProxy ssp = Recents.getSystemServices();
    362         MutableBoolean isHomeStackVisible = new MutableBoolean(true);
    363         if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
    364             ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
    365             RecentsTaskLoader loader = Recents.getTaskLoader();
    366             sInstanceLoadPlan = loader.createLoadPlan(mContext);
    367             sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
    368             loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
    369             TaskStack stack = sInstanceLoadPlan.getTaskStack();
    370             if (stack.getTaskCount() > 0) {
    371                 // Only preload the icon (but not the thumbnail since it may not have been taken for
    372                 // the pausing activity)
    373                 preloadIcon(runningTask.id);
    374 
    375                 // At this point, we don't know anything about the stack state.  So only calculate
    376                 // the dimensions of the thumbnail that we need for the transition into Recents, but
    377                 // do not draw it until we construct the activity options when we start Recents
    378                 updateHeaderBarLayout(stack, null /* window rect override*/);
    379             }
    380         }
    381     }
    382 
    383     public void cancelPreloadingRecents() {
    384         // Do nothing
    385     }
    386 
    387     public void onDraggingInRecents(float distanceFromTop) {
    388         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
    389     }
    390 
    391     public void onDraggingInRecentsEnded(float velocity) {
    392         EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
    393     }
    394 
    395     /**
    396      * Transitions to the next recent task in the stack.
    397      */
    398     public void showNextTask() {
    399         SystemServicesProxy ssp = Recents.getSystemServices();
    400         RecentsTaskLoader loader = Recents.getTaskLoader();
    401         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
    402         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
    403         TaskStack focusedStack = plan.getTaskStack();
    404 
    405         // Return early if there are no tasks in the focused stack
    406         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
    407 
    408         // Return early if there is no running task
    409         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
    410         if (runningTask == null) return;
    411 
    412         // Find the task in the recents list
    413         boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
    414         ArrayList<Task> tasks = focusedStack.getStackTasks();
    415         Task toTask = null;
    416         ActivityOptions launchOpts = null;
    417         int taskCount = tasks.size();
    418         for (int i = taskCount - 1; i >= 1; i--) {
    419             Task task = tasks.get(i);
    420             if (isRunningTaskInHomeStack) {
    421                 toTask = tasks.get(i - 1);
    422                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
    423                         R.anim.recents_launch_next_affiliated_task_target,
    424                         R.anim.recents_fast_toggle_app_home_exit);
    425                 break;
    426             } else if (task.key.id == runningTask.id) {
    427                 toTask = tasks.get(i - 1);
    428                 launchOpts = ActivityOptions.makeCustomAnimation(mContext,
    429                         R.anim.recents_launch_prev_affiliated_task_target,
    430                         R.anim.recents_launch_prev_affiliated_task_source);
    431                 break;
    432             }
    433         }
    434 
    435         // Return early if there is no next task
    436         if (toTask == null) {
    437             ssp.startInPlaceAnimationOnFrontMostApplication(
    438                     ActivityOptions.makeCustomInPlaceAnimation(mContext,
    439                             R.anim.recents_launch_prev_affiliated_task_bounce));
    440             return;
    441         }
    442 
    443         // Launch the task
    444         ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
    445     }
    446 
    447     /**
    448      * Transitions to the next affiliated task.
    449      */
    450     public void showRelativeAffiliatedTask(boolean showNextTask) {
    451         SystemServicesProxy ssp = Recents.getSystemServices();
    452         RecentsTaskLoader loader = Recents.getTaskLoader();
    453         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
    454         loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
    455         TaskStack focusedStack = plan.getTaskStack();
    456 
    457         // Return early if there are no tasks in the focused stack
    458         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
    459 
    460         // Return early if there is no running task (can't determine affiliated tasks in this case)
    461         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
    462         if (runningTask == null) return;
    463         // Return early if the running task is in the home stack (optimization)
    464         if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
    465 
    466         // Find the task in the recents list
    467         ArrayList<Task> tasks = focusedStack.getStackTasks();
    468         Task toTask = null;
    469         ActivityOptions launchOpts = null;
    470         int taskCount = tasks.size();
    471         int numAffiliatedTasks = 0;
    472         for (int i = 0; i < taskCount; i++) {
    473             Task task = tasks.get(i);
    474             if (task.key.id == runningTask.id) {
    475                 TaskGrouping group = task.group;
    476                 Task.TaskKey toTaskKey;
    477                 if (showNextTask) {
    478                     toTaskKey = group.getNextTaskInGroup(task);
    479                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
    480                             R.anim.recents_launch_next_affiliated_task_target,
    481                             R.anim.recents_launch_next_affiliated_task_source);
    482                 } else {
    483                     toTaskKey = group.getPrevTaskInGroup(task);
    484                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
    485                             R.anim.recents_launch_prev_affiliated_task_target,
    486                             R.anim.recents_launch_prev_affiliated_task_source);
    487                 }
    488                 if (toTaskKey != null) {
    489                     toTask = focusedStack.findTaskWithId(toTaskKey.id);
    490                 }
    491                 numAffiliatedTasks = group.getTaskCount();
    492                 break;
    493             }
    494         }
    495 
    496         // Return early if there is no next task
    497         if (toTask == null) {
    498             if (numAffiliatedTasks > 1) {
    499                 if (showNextTask) {
    500                     ssp.startInPlaceAnimationOnFrontMostApplication(
    501                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
    502                                     R.anim.recents_launch_next_affiliated_task_bounce));
    503                 } else {
    504                     ssp.startInPlaceAnimationOnFrontMostApplication(
    505                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
    506                                     R.anim.recents_launch_prev_affiliated_task_bounce));
    507                 }
    508             }
    509             return;
    510         }
    511 
    512         // Keep track of actually launched affiliated tasks
    513         MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
    514 
    515         // Launch the task
    516         ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
    517     }
    518 
    519     public void showNextAffiliatedTask() {
    520         // Keep track of when the affiliated task is triggered
    521         MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
    522         showRelativeAffiliatedTask(true);
    523     }
    524 
    525     public void showPrevAffiliatedTask() {
    526         // Keep track of when the affiliated task is triggered
    527         MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
    528         showRelativeAffiliatedTask(false);
    529     }
    530 
    531     public void dockTopTask(int topTaskId, int dragMode,
    532             int stackCreateMode, Rect initialBounds) {
    533         SystemServicesProxy ssp = Recents.getSystemServices();
    534 
    535         // Make sure we inform DividerView before we actually start the activity so we can change
    536         // the resize mode already.
    537         if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
    538             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
    539             showRecents(
    540                     false /* triggeredFromAltTab */,
    541                     dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
    542                     false /* animate */,
    543                     true /* launchedWhileDockingTask*/,
    544                     false /* fromHome */,
    545                     DividerView.INVALID_RECENTS_GROW_TARGET);
    546         }
    547     }
    548 
    549     /**
    550      * Returns the preloaded load plan and invalidates it.
    551      */
    552     public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
    553         RecentsTaskLoadPlan plan = sInstanceLoadPlan;
    554         sInstanceLoadPlan = null;
    555         return plan;
    556     }
    557 
    558     /**
    559      * Reloads all the resources for the current configuration.
    560      */
    561     private void reloadResources() {
    562         Resources res = mContext.getResources();
    563 
    564         mStatusBarHeight = res.getDimensionPixelSize(
    565                 com.android.internal.R.dimen.status_bar_height);
    566         mNavBarHeight = res.getDimensionPixelSize(
    567                 com.android.internal.R.dimen.navigation_bar_height);
    568         mNavBarWidth = res.getDimensionPixelSize(
    569                 com.android.internal.R.dimen.navigation_bar_width);
    570         mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
    571                 R.dimen.recents_task_view_header_height,
    572                 R.dimen.recents_task_view_header_height,
    573                 R.dimen.recents_task_view_header_height,
    574                 R.dimen.recents_task_view_header_height_tablet_land,
    575                 R.dimen.recents_task_view_header_height,
    576                 R.dimen.recents_task_view_header_height_tablet_land);
    577     }
    578 
    579     /**
    580      * Prepares the header bar layout for the next transition, if the task view bounds has changed
    581      * since the last call, it will attempt to re-measure and layout the header bar to the new size.
    582      *
    583      * @param stack the stack to initialize the stack layout with
    584      * @param windowRectOverride the rectangle to use when calculating the stack state which can
    585      *                           be different from the current window rect if recents is resizing
    586      *                           while being launched
    587      */
    588     private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
    589         SystemServicesProxy ssp = Recents.getSystemServices();
    590         Rect displayRect = ssp.getDisplayRect();
    591         Rect systemInsets = new Rect();
    592         ssp.getStableInsets(systemInsets);
    593         Rect windowRect = windowRectOverride != null
    594                 ? new Rect(windowRectOverride)
    595                 : ssp.getWindowRect();
    596         // When docked, the nav bar insets are consumed and the activity is measured without insets.
    597         // However, the window bounds include the insets, so we need to subtract them here to make
    598         // them identical.
    599         if (ssp.hasDockedTask()) {
    600             windowRect.bottom -= systemInsets.bottom;
    601             systemInsets.bottom = 0;
    602         }
    603         calculateWindowStableInsets(systemInsets, windowRect);
    604         windowRect.offsetTo(0, 0);
    605 
    606         TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
    607 
    608         // Rebind the header bar and draw it for the transition
    609         stackLayout.setSystemInsets(systemInsets);
    610         if (stack != null) {
    611             stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
    612                     systemInsets.left, systemInsets.right, mTaskStackBounds);
    613             stackLayout.reset();
    614             stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
    615                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
    616             mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
    617 
    618             Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
    619             if (!taskViewBounds.isEmpty()) {
    620                 int taskViewWidth = taskViewBounds.width();
    621                 synchronized (mHeaderBarLock) {
    622                     if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
    623                             mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
    624                         mHeaderBar.measure(
    625                                 MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
    626                                 MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
    627                     }
    628                     mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
    629                 }
    630 
    631                 // Update the transition bitmap to match the new header bar height
    632                 if (mThumbTransitionBitmapCache == null ||
    633                         (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
    634                         (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
    635                     mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
    636                             mTaskBarHeight, Bitmap.Config.ARGB_8888);
    637                 }
    638             }
    639         }
    640     }
    641 
    642     /**
    643      * Given the stable insets and the rect for our window, calculates the insets that affect our
    644      * window.
    645      */
    646     private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
    647         Rect displayRect = Recents.getSystemServices().getDisplayRect();
    648 
    649         // Display rect without insets - available app space
    650         Rect appRect = new Rect(displayRect);
    651         appRect.inset(inOutInsets);
    652 
    653         // Our window intersected with available app space
    654         Rect windowRectWithInsets = new Rect(windowRect);
    655         windowRectWithInsets.intersect(appRect);
    656         inOutInsets.left = windowRectWithInsets.left - windowRect.left;
    657         inOutInsets.top = windowRectWithInsets.top - windowRect.top;
    658         inOutInsets.right = windowRect.right - windowRectWithInsets.right;
    659         inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
    660     }
    661 
    662     /**
    663      * Preloads the icon of a task.
    664      */
    665     private void preloadIcon(int runningTaskId) {
    666         // Ensure that we load the running task's icon
    667         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
    668         launchOpts.runningTaskId = runningTaskId;
    669         launchOpts.loadThumbnails = false;
    670         launchOpts.onlyLoadForCache = true;
    671         Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
    672     }
    673 
    674     /**
    675      * Creates the activity options for a unknown state->recents transition.
    676      */
    677     protected ActivityOptions getUnknownTransitionActivityOptions() {
    678         return ActivityOptions.makeCustomAnimation(mContext,
    679                 R.anim.recents_from_unknown_enter,
    680                 R.anim.recents_from_unknown_exit,
    681                 mHandler, null);
    682     }
    683 
    684     /**
    685      * Creates the activity options for a home->recents transition.
    686      */
    687     protected ActivityOptions getHomeTransitionActivityOptions() {
    688         return ActivityOptions.makeCustomAnimation(mContext,
    689                 R.anim.recents_from_launcher_enter,
    690                 R.anim.recents_from_launcher_exit,
    691                 mHandler, null);
    692     }
    693 
    694     /**
    695      * Creates the activity options for an app->recents transition.
    696      */
    697     private ActivityOptions getThumbnailTransitionActivityOptions(
    698             ActivityManager.RunningTaskInfo runningTask, TaskStackView stackView,
    699                     Rect windowOverrideRect) {
    700         if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
    701             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
    702             ArrayList<Task> tasks = stackView.getStack().getStackTasks();
    703             TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
    704             TaskStackViewScroller stackScroller = stackView.getScroller();
    705 
    706             stackView.updateLayoutAlgorithm(true /* boundScroll */);
    707             stackView.updateToInitialState();
    708 
    709             for (int i = tasks.size() - 1; i >= 0; i--) {
    710                 Task task = tasks.get(i);
    711                 if (task.isFreeformTask()) {
    712                     mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
    713                             stackScroller.getStackScroll(), mTmpTransform, null,
    714                             windowOverrideRect);
    715                     Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
    716                             mThumbTransitionBitmapCache);
    717                     Rect toTaskRect = new Rect();
    718                     mTmpTransform.rect.round(toTaskRect);
    719                     specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
    720                 }
    721             }
    722             AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
    723             specs.toArray(specsArray);
    724             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
    725                     specsArray, mHandler, null, this);
    726         } else {
    727             // Update the destination rect
    728             Task toTask = new Task();
    729             TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
    730                     windowOverrideRect);
    731             Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
    732                     mThumbTransitionBitmapCache);
    733             if (thumbnail != null) {
    734                 RectF toTaskRect = toTransform.rect;
    735                 return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
    736                         thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
    737                         (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
    738             }
    739             // If both the screenshot and thumbnail fails, then just fall back to the default transition
    740             return getUnknownTransitionActivityOptions();
    741         }
    742     }
    743 
    744     /**
    745      * Returns the transition rect for the given task id.
    746      */
    747     private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
    748             Task runningTaskOut, Rect windowOverrideRect) {
    749         // Find the running task in the TaskStack
    750         TaskStack stack = stackView.getStack();
    751         Task launchTask = stack.getLaunchTarget();
    752         if (launchTask != null) {
    753             runningTaskOut.copyFrom(launchTask);
    754         } else {
    755             // If no task is specified or we can not find the task just use the front most one
    756             launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
    757             runningTaskOut.copyFrom(launchTask);
    758         }
    759 
    760         // Get the transform for the running task
    761         stackView.updateLayoutAlgorithm(true /* boundScroll */);
    762         stackView.updateToInitialState();
    763         stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
    764                 stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
    765         return mTmpTransform;
    766     }
    767 
    768     /**
    769      * Draws the header of a task used for the window animation into a bitmap.
    770      */
    771     private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
    772             Bitmap thumbnail) {
    773         SystemServicesProxy ssp = Recents.getSystemServices();
    774         if (toTransform != null && toTask.key != null) {
    775             synchronized (mHeaderBarLock) {
    776                 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
    777                 mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
    778                         (int) toTransform.rect.height());
    779                 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
    780                     thumbnail.eraseColor(0xFFff0000);
    781                 } else {
    782                     thumbnail.eraseColor(0);
    783                     Canvas c = new Canvas(thumbnail);
    784                     // Workaround for b/27815919, reset the callback so that we do not trigger an
    785                     // invalidate on the header bar as a result of updating the icon
    786                     Drawable icon = mHeaderBar.getIconView().getDrawable();
    787                     if (icon != null) {
    788                         icon.setCallback(null);
    789                     }
    790                     mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
    791                             disabledInSafeMode);
    792                     mHeaderBar.onTaskDataLoaded();
    793                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
    794                     mHeaderBar.draw(c);
    795                     c.setBitmap(null);
    796                 }
    797             }
    798             return thumbnail.createAshmemBitmap();
    799         }
    800         return null;
    801     }
    802 
    803     /**
    804      * Shows the recents activity
    805      */
    806     protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
    807             boolean isHomeStackVisible, boolean animate, int growTarget) {
    808         RecentsTaskLoader loader = Recents.getTaskLoader();
    809         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
    810         SystemServicesProxy ssp = Recents.getSystemServices();
    811         boolean isBlacklisted = (runningTask != null)
    812                 ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
    813                 : false;
    814 
    815         int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
    816                 ? runningTask.id
    817                 : -1;
    818 
    819         // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
    820         // should always preload the tasks now. If we are dragging in recents, reload them as
    821         // the stacks might have changed.
    822         if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
    823             // Create a new load plan if preloadRecents() was never triggered
    824             sInstanceLoadPlan = loader.createLoadPlan(mContext);
    825         }
    826         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
    827             loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
    828         }
    829 
    830         TaskStack stack = sInstanceLoadPlan.getTaskStack();
    831         boolean hasRecentTasks = stack.getTaskCount() > 0;
    832         boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
    833                 hasRecentTasks;
    834 
    835         // Update the launch state that we need in updateHeaderBarLayout()
    836         launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
    837         launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
    838         launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
    839         launchState.launchedViaDockGesture = mLaunchedWhileDocking;
    840         launchState.launchedViaDragGesture = mDraggingInRecents;
    841         launchState.launchedToTaskId = runningTaskId;
    842         launchState.launchedWithAltTab = mTriggeredFromAltTab;
    843 
    844         // Preload the icon (this will be a null-op if we have preloaded the icon already in
    845         // preloadRecents())
    846         preloadIcon(runningTaskId);
    847 
    848         // Update the header bar if necessary
    849         Rect windowOverrideRect = getWindowRectOverride(growTarget);
    850         updateHeaderBarLayout(stack, windowOverrideRect);
    851 
    852         // Prepare the dummy stack for the transition
    853         TaskStackLayoutAlgorithm.VisibilityReport stackVr =
    854                 mDummyStackView.computeStackVisibilityReport();
    855 
    856         // Update the remaining launch state
    857         launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
    858         launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
    859 
    860         if (!animate) {
    861             startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
    862             return;
    863         }
    864 
    865         ActivityOptions opts;
    866         if (isBlacklisted) {
    867             opts = getUnknownTransitionActivityOptions();
    868         } else if (useThumbnailTransition) {
    869             // Try starting with a thumbnail transition
    870             opts = getThumbnailTransitionActivityOptions(runningTask, mDummyStackView,
    871                     windowOverrideRect);
    872         } else {
    873             // If there is no thumbnail transition, but is launching from home into recents, then
    874             // use a quick home transition
    875             opts = hasRecentTasks
    876                 ? getHomeTransitionActivityOptions()
    877                 : getUnknownTransitionActivityOptions();
    878         }
    879         startRecentsActivity(opts);
    880         mLastToggleTime = SystemClock.elapsedRealtime();
    881     }
    882 
    883     private Rect getWindowRectOverride(int growTarget) {
    884         if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
    885             return null;
    886         }
    887         Rect result = new Rect();
    888         Rect displayRect = Recents.getSystemServices().getDisplayRect();
    889         DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
    890                 result, displayRect.width(), displayRect.height(),
    891                 Recents.getSystemServices().getDockedDividerSize(mContext));
    892         return result;
    893     }
    894 
    895     /**
    896      * Starts the recents activity.
    897      */
    898     private void startRecentsActivity(ActivityOptions opts) {
    899         Intent intent = new Intent();
    900         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
    901         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
    902                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    903                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
    904 
    905         if (opts != null) {
    906             mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
    907         } else {
    908             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    909         }
    910         EventBus.getDefault().send(new RecentsActivityStartingEvent());
    911     }
    912 
    913     /**** OnAnimationFinishedListener Implementation ****/
    914 
    915     @Override
    916     public void onAnimationFinished() {
    917         EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
    918     }
    919 }
    920