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