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