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