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