1 /*
2  * Copyright (C) 2014 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 android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.ActivityOptions;
22 import android.app.ITaskStackListener;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.ActivityNotFoundException;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.Canvas;
33 import android.graphics.Rect;
34 import android.os.AsyncTask;
35 import android.os.Handler;
36 import android.os.SystemClock;
37 import android.os.UserHandle;
38 import android.util.MutableBoolean;
39 import android.view.Display;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.systemui.Prefs;
45 import com.android.systemui.R;
46 import com.android.systemui.RecentsComponent;
47 import com.android.systemui.SystemUI;
48 import com.android.systemui.SystemUIApplication;
49 import com.android.systemui.recents.misc.Console;
50 import com.android.systemui.recents.misc.SystemServicesProxy;
51 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
52 import com.android.systemui.recents.model.RecentsTaskLoader;
53 import com.android.systemui.recents.model.Task;
54 import com.android.systemui.recents.model.TaskGrouping;
55 import com.android.systemui.recents.model.TaskStack;
56 import com.android.systemui.recents.views.TaskStackView;
57 import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
58 import com.android.systemui.recents.views.TaskViewHeader;
59 import com.android.systemui.recents.views.TaskViewTransform;
60 import com.android.systemui.statusbar.phone.PhoneStatusBar;
61 
62 import java.util.ArrayList;
63 
64 /**
65  * Annotation for a method that is only called from the primary user's SystemUI process and will be
66  * proxied to the current user.
67  */
68 @interface ProxyFromPrimaryToCurrentUser {}
69 /**
70  * Annotation for a method that may be called from any user's SystemUI process and will be proxied
71  * to the primary user.
72  */
73 @interface ProxyFromAnyToPrimaryUser {}
74 
75 /** A proxy implementation for the recents component */
76 public class Recents extends SystemUI
77         implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
78 
79     final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
80     final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
81     final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
82 
83     // Owner proxy events
84     final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
85             "action_notify_recents_visibility_change";
86     final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
87             "action_screen_pinning_request";
88 
89     final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
90     final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
91     final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
92 
93     final static int sMinToggleDelay = 350;
94 
95     public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
96     public final static String sRecentsPackage = "com.android.systemui";
97     public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
98 
99     /**
100      * An implementation of ITaskStackListener, that allows us to listen for changes to the system
101      * task stacks and update recents accordingly.
102      */
103     class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
104         Handler mHandler;
105 
TaskStackListenerImpl(Handler handler)106         public TaskStackListenerImpl(Handler handler) {
107             mHandler = handler;
108         }
109 
110         @Override
onTaskStackChanged()111         public void onTaskStackChanged() {
112             // Debounce any task stack changes
113             mHandler.removeCallbacks(this);
114             mHandler.post(this);
115         }
116 
117         /** Preloads the next task */
run()118         public void run() {
119             // Temporarily skip this if multi stack is enabled
120             if (mConfig.multiStackEnabled) return;
121 
122             RecentsConfiguration config = RecentsConfiguration.getInstance();
123             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
124                 RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
125                 SystemServicesProxy ssp = loader.getSystemServicesProxy();
126                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
127 
128                 // Load the next task only if we aren't svelte
129                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
130                 loader.preloadTasks(plan, true /* isTopTaskHome */);
131                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
132                 // This callback is made when a new activity is launched and the old one is paused
133                 // so ignore the current activity and try and preload the thumbnail for the
134                 // previous one.
135                 if (runningTaskInfo != null) {
136                     launchOpts.runningTaskId = runningTaskInfo.id;
137                 }
138                 launchOpts.numVisibleTasks = 2;
139                 launchOpts.numVisibleTaskThumbnails = 2;
140                 launchOpts.onlyLoadForCache = true;
141                 launchOpts.onlyLoadPausedActivities = true;
142                 loader.loadTasks(mContext, plan, launchOpts);
143             }
144         }
145     }
146 
147     /**
148      * A proxy for Recents events which happens strictly for the owner.
149      */
150     class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
151         @Override
onReceive(Context context, Intent intent)152         public void onReceive(Context context, Intent intent) {
153             switch (intent.getAction()) {
154                 case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
155                     visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
156                     break;
157                 case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
158                     onStartScreenPinning(context);
159                     break;
160             }
161         }
162     }
163 
164     static RecentsComponent.Callbacks sRecentsComponentCallbacks;
165     static RecentsTaskLoadPlan sInstanceLoadPlan;
166     static Recents sInstance;
167 
168     LayoutInflater mInflater;
169     SystemServicesProxy mSystemServicesProxy;
170     Handler mHandler;
171     TaskStackListenerImpl mTaskStackListener;
172     RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
173     RecentsAppWidgetHost mAppWidgetHost;
174     boolean mBootCompleted;
175     boolean mStartAnimationTriggered;
176     boolean mCanReuseTaskStackViews = true;
177 
178     // Task launching
179     RecentsConfiguration mConfig;
180     Rect mWindowRect = new Rect();
181     Rect mTaskStackBounds = new Rect();
182     Rect mSystemInsets = new Rect();
183     TaskViewTransform mTmpTransform = new TaskViewTransform();
184     int mStatusBarHeight;
185     int mNavBarHeight;
186     int mNavBarWidth;
187 
188     // Header (for transition)
189     TaskViewHeader mHeaderBar;
190     final Object mHeaderBarLock = new Object();
191     TaskStackView mDummyStackView;
192 
193     // Variables to keep track of if we need to start recents after binding
194     boolean mTriggeredFromAltTab;
195     long mLastToggleTime;
196 
197     Bitmap mThumbnailTransitionBitmapCache;
198     Task mThumbnailTransitionBitmapCacheKey;
199 
Recents()200     public Recents() {
201     }
202 
203     /**
204      * Gets the singleton instance and starts it if needed. On the primary user on the device, this
205      * component gets started as a normal {@link SystemUI} component. On a secondary user, this
206      * lifecycle doesn't exist, so we need to start it manually here if needed.
207      */
getInstanceAndStartIfNeeded(Context ctx)208     public static Recents getInstanceAndStartIfNeeded(Context ctx) {
209         if (sInstance == null) {
210             sInstance = new Recents();
211             sInstance.mContext = ctx;
212             sInstance.start();
213             sInstance.onBootCompleted();
214         }
215         return sInstance;
216     }
217 
218     /** Creates a new broadcast intent */
createLocalBroadcastIntent(Context context, String action)219     static Intent createLocalBroadcastIntent(Context context, String action) {
220         Intent intent = new Intent(action);
221         intent.setPackage(context.getPackageName());
222         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
223                 Intent.FLAG_RECEIVER_FOREGROUND);
224         return intent;
225     }
226 
227     /** Initializes the Recents. */
228     @ProxyFromPrimaryToCurrentUser
229     @Override
start()230     public void start() {
231         if (sInstance == null) {
232             sInstance = this;
233         }
234         RecentsTaskLoader.initialize(mContext);
235         mInflater = LayoutInflater.from(mContext);
236         mSystemServicesProxy = new SystemServicesProxy(mContext);
237         mHandler = new Handler();
238         mTaskStackBounds = new Rect();
239         mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
240 
241         // Register the task stack listener
242         mTaskStackListener = new TaskStackListenerImpl(mHandler);
243         mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
244 
245         // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
246         // instances of AlternateRecentsComponent needs to notify the owner when the visibility
247         // changes.
248         if (mSystemServicesProxy.isForegroundUserOwner()) {
249             mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
250             IntentFilter filter = new IntentFilter();
251             filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
252             filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
253             mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
254                     null, mHandler);
255         }
256 
257         // Initialize some static datastructures
258         TaskStackViewLayoutAlgorithm.initializeCurve();
259         // Load the header bar layout
260         reloadHeaderBarLayout();
261 
262         // When we start, preload the data associated with the previous recent tasks.
263         // We can use a new plan since the caches will be the same.
264         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
265         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
266         loader.preloadTasks(plan, true /* isTopTaskHome */);
267         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
268         launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
269         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
270         launchOpts.onlyLoadForCache = true;
271         loader.loadTasks(mContext, plan, launchOpts);
272         putComponent(Recents.class, this);
273     }
274 
275     @Override
onBootCompleted()276     public void onBootCompleted() {
277         mBootCompleted = true;
278     }
279 
280     /** Shows the Recents. */
281     @ProxyFromPrimaryToCurrentUser
282     @Override
showRecents(boolean triggeredFromAltTab, View statusBarView)283     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
284         if (mSystemServicesProxy.isForegroundUserOwner()) {
285             showRecentsInternal(triggeredFromAltTab);
286         } else {
287             Intent intent = createLocalBroadcastIntent(mContext,
288                     RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
289             intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
290             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
291         }
292     }
293 
showRecentsInternal(boolean triggeredFromAltTab)294     void showRecentsInternal(boolean triggeredFromAltTab) {
295         mTriggeredFromAltTab = triggeredFromAltTab;
296 
297         try {
298             startRecentsActivity();
299         } catch (ActivityNotFoundException e) {
300             Console.logRawError("Failed to launch RecentAppsIntent", e);
301         }
302     }
303 
304     /** Hides the Recents. */
305     @ProxyFromPrimaryToCurrentUser
306     @Override
hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)307     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
308         if (mSystemServicesProxy.isForegroundUserOwner()) {
309             hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
310         } else {
311             Intent intent = createLocalBroadcastIntent(mContext,
312                     RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
313             intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
314             intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
315             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
316         }
317     }
318 
hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)319     void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
320         if (mBootCompleted) {
321             // Defer to the activity to handle hiding recents, if it handles it, then it must still
322             // be visible
323             Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
324             intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
325             intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
326             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
327         }
328     }
329 
330     /** Toggles the Recents activity. */
331     @ProxyFromPrimaryToCurrentUser
332     @Override
toggleRecents(Display display, int layoutDirection, View statusBarView)333     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
334         if (mSystemServicesProxy.isForegroundUserOwner()) {
335             toggleRecentsInternal();
336         } else {
337             Intent intent = createLocalBroadcastIntent(mContext,
338                     RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
339             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
340         }
341     }
342 
toggleRecentsInternal()343     void toggleRecentsInternal() {
344         mTriggeredFromAltTab = false;
345 
346         try {
347             toggleRecentsActivity();
348         } catch (ActivityNotFoundException e) {
349             Console.logRawError("Failed to launch RecentAppsIntent", e);
350         }
351     }
352 
353     /** Preloads info for the Recents activity. */
354     @ProxyFromPrimaryToCurrentUser
355     @Override
preloadRecents()356     public void preloadRecents() {
357         if (mSystemServicesProxy.isForegroundUserOwner()) {
358             preloadRecentsInternal();
359         } else {
360             Intent intent = createLocalBroadcastIntent(mContext,
361                     RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
362             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
363         }
364     }
365 
preloadRecentsInternal()366     void preloadRecentsInternal() {
367         // Preload only the raw task list into a new load plan (which will be consumed by the
368         // RecentsActivity) only if there is a task to animate to.
369         ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
370         MutableBoolean topTaskHome = new MutableBoolean(true);
371         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
372         sInstanceLoadPlan = loader.createLoadPlan(mContext);
373         if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
374             sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
375             loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
376             TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
377             if (top.getTaskCount() > 0) {
378                 preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
379                         topTaskHome.value);
380             }
381         }
382     }
383 
384     @Override
cancelPreloadingRecents()385     public void cancelPreloadingRecents() {
386         // Do nothing
387     }
388 
showRelativeAffiliatedTask(boolean showNextTask)389     void showRelativeAffiliatedTask(boolean showNextTask) {
390         // Return early if there is no focused stack
391         int focusedStackId = mSystemServicesProxy.getFocusedStack();
392         TaskStack focusedStack = null;
393         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
394         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
395         loader.preloadTasks(plan, true /* isTopTaskHome */);
396         if (mConfig.multiStackEnabled) {
397             if (focusedStackId < 0) return;
398             focusedStack = plan.getTaskStack(focusedStackId);
399         } else {
400             focusedStack = plan.getAllTaskStacks().get(0);
401         }
402 
403         // Return early if there are no tasks in the focused stack
404         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
405 
406         ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
407         // Return early if there is no running task (can't determine affiliated tasks in this case)
408         if (runningTask == null) return;
409         // Return early if the running task is in the home stack (optimization)
410         if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
411 
412         // Find the task in the recents list
413         ArrayList<Task> tasks = focusedStack.getTasks();
414         Task toTask = null;
415         ActivityOptions launchOpts = null;
416         int taskCount = tasks.size();
417         int numAffiliatedTasks = 0;
418         for (int i = 0; i < taskCount; i++) {
419             Task task = tasks.get(i);
420             if (task.key.id == runningTask.id) {
421                 TaskGrouping group = task.group;
422                 Task.TaskKey toTaskKey;
423                 if (showNextTask) {
424                     toTaskKey = group.getNextTaskInGroup(task);
425                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
426                             R.anim.recents_launch_next_affiliated_task_target,
427                             R.anim.recents_launch_next_affiliated_task_source);
428                 } else {
429                     toTaskKey = group.getPrevTaskInGroup(task);
430                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
431                             R.anim.recents_launch_prev_affiliated_task_target,
432                             R.anim.recents_launch_prev_affiliated_task_source);
433                 }
434                 if (toTaskKey != null) {
435                     toTask = focusedStack.findTaskWithId(toTaskKey.id);
436                 }
437                 numAffiliatedTasks = group.getTaskCount();
438                 break;
439             }
440         }
441 
442         // Return early if there is no next task
443         if (toTask == null) {
444             if (numAffiliatedTasks > 1) {
445                 if (showNextTask) {
446                     mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
447                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
448                                     R.anim.recents_launch_next_affiliated_task_bounce));
449                 } else {
450                     mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
451                             ActivityOptions.makeCustomInPlaceAnimation(mContext,
452                                     R.anim.recents_launch_prev_affiliated_task_bounce));
453                 }
454             }
455             return;
456         }
457 
458         // Keep track of actually launched affiliated tasks
459         MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
460 
461         // Launch the task
462         if (toTask.isActive) {
463             // Bring an active task to the foreground
464             mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
465         } else {
466             mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
467                     toTask.activityLabel, launchOpts);
468         }
469     }
470 
471     @Override
showNextAffiliatedTask()472     public void showNextAffiliatedTask() {
473         // Keep track of when the affiliated task is triggered
474         MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
475         showRelativeAffiliatedTask(true);
476     }
477 
478     @Override
showPrevAffiliatedTask()479     public void showPrevAffiliatedTask() {
480         // Keep track of when the affiliated task is triggered
481         MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
482         showRelativeAffiliatedTask(false);
483     }
484 
485     /** Updates on configuration change. */
486     @ProxyFromPrimaryToCurrentUser
onConfigurationChanged(Configuration newConfig)487     public void onConfigurationChanged(Configuration newConfig) {
488         if (mSystemServicesProxy.isForegroundUserOwner()) {
489             configurationChanged();
490         } else {
491             Intent intent = createLocalBroadcastIntent(mContext,
492                     RecentsUserEventProxyReceiver.ACTION_PROXY_CONFIG_CHANGE_TO_USER);
493             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
494         }
495     }
configurationChanged()496     void configurationChanged() {
497         // Don't reuse task stack views if the configuration changes
498         mCanReuseTaskStackViews = false;
499         // Reload the header bar layout
500         reloadHeaderBarLayout();
501     }
502 
503     /** Prepares the header bar layout. */
reloadHeaderBarLayout()504     void reloadHeaderBarLayout() {
505         Resources res = mContext.getResources();
506         mWindowRect = mSystemServicesProxy.getWindowRect();
507         mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
508         mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
509         mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
510         mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
511         mConfig.updateOnConfigurationChange();
512         Rect searchBarBounds = new Rect();
513         // Try and pre-emptively bind the search widget on startup to ensure that we
514         // have the right thumbnail bounds to animate to.
515         // Note: We have to reload the widget id before we get the task stack bounds below
516         if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
517             mConfig.getSearchBarBounds(mWindowRect.width(), mWindowRect.height(),
518                     mStatusBarHeight, searchBarBounds);
519         }
520         mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(),
521                 mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), searchBarBounds,
522                 mTaskStackBounds);
523         if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
524             mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
525         } else {
526             mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
527         }
528 
529         // Inflate the header bar layout so that we can rebind and draw it for the transition
530         TaskStack stack = new TaskStack();
531         mDummyStackView = new TaskStackView(mContext, stack);
532         TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
533         Rect taskStackBounds = new Rect(mTaskStackBounds);
534         taskStackBounds.bottom -= mSystemInsets.bottom;
535         algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
536         Rect taskViewSize = algo.getUntransformedTaskViewSize();
537         int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
538         synchronized (mHeaderBarLock) {
539             mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
540                     false);
541             mHeaderBar.measure(
542                     View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
543                     View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
544             mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
545         }
546     }
547 
548     /** Toggles the recents activity */
toggleRecentsActivity()549     void toggleRecentsActivity() {
550         // If the user has toggled it too quickly, then just eat up the event here (it's better than
551         // showing a janky screenshot).
552         // NOTE: Ideally, the screenshot mechanism would take the window transform into account
553         if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
554             return;
555         }
556 
557         // If Recents is the front most activity, then we should just communicate with it directly
558         // to launch the first task or dismiss itself
559         ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
560         MutableBoolean isTopTaskHome = new MutableBoolean(true);
561         if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
562             // Notify recents to toggle itself
563             Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
564             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
565             mLastToggleTime = SystemClock.elapsedRealtime();
566             return;
567         } else {
568             // Otherwise, start the recents activity
569             startRecentsActivity(topTask, isTopTaskHome.value);
570         }
571     }
572 
573     /** Starts the recents activity if it is not already running */
startRecentsActivity()574     void startRecentsActivity() {
575         // Check if the top task is in the home stack, and start the recents activity
576         ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
577         MutableBoolean isTopTaskHome = new MutableBoolean(true);
578         if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
579             startRecentsActivity(topTask, isTopTaskHome.value);
580         }
581     }
582 
583     /**
584      * Creates the activity options for a unknown state->recents transition.
585      */
getUnknownTransitionActivityOptions()586     ActivityOptions getUnknownTransitionActivityOptions() {
587         mStartAnimationTriggered = false;
588         return ActivityOptions.makeCustomAnimation(mContext,
589                 R.anim.recents_from_unknown_enter,
590                 R.anim.recents_from_unknown_exit,
591                 mHandler, this);
592     }
593 
594     /**
595      * Creates the activity options for a home->recents transition.
596      */
getHomeTransitionActivityOptions(boolean fromSearchHome)597     ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
598         mStartAnimationTriggered = false;
599         if (fromSearchHome) {
600             return ActivityOptions.makeCustomAnimation(mContext,
601                     R.anim.recents_from_search_launcher_enter,
602                     R.anim.recents_from_search_launcher_exit,
603                     mHandler, this);
604         }
605         return ActivityOptions.makeCustomAnimation(mContext,
606                 R.anim.recents_from_launcher_enter,
607                 R.anim.recents_from_launcher_exit,
608                 mHandler, this);
609     }
610 
611     /**
612      * Creates the activity options for an app->recents transition.
613      */
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView)614     ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
615             TaskStack stack, TaskStackView stackView) {
616 
617         // Update the destination rect
618         Task toTask = new Task();
619         TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
620                 topTask.id, toTask);
621         Rect toTaskRect = toTransform.rect;
622         Bitmap thumbnail;
623         if (mThumbnailTransitionBitmapCacheKey != null
624                 && mThumbnailTransitionBitmapCacheKey.key != null
625                 && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
626             thumbnail = mThumbnailTransitionBitmapCache;
627             mThumbnailTransitionBitmapCacheKey = null;
628             mThumbnailTransitionBitmapCache = null;
629         } else {
630             preloadIcon(topTask);
631             thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
632         }
633         if (thumbnail != null) {
634             mStartAnimationTriggered = false;
635             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
636                     thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
637                     toTaskRect.height(), mHandler, this);
638         }
639 
640         // If both the screenshot and thumbnail fails, then just fall back to the default transition
641         return getUnknownTransitionActivityOptions();
642     }
643 
644     /**
645      * Preloads the icon of a task.
646      */
preloadIcon(ActivityManager.RunningTaskInfo task)647     void preloadIcon(ActivityManager.RunningTaskInfo task) {
648 
649         // Ensure that we load the running task's icon
650         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
651         launchOpts.runningTaskId = task.id;
652         launchOpts.loadThumbnails = false;
653         launchOpts.onlyLoadForCache = true;
654         RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
655     }
656 
657     /**
658      * Caches the header thumbnail used for a window animation asynchronously into
659      * {@link #mThumbnailTransitionBitmapCache}.
660      */
preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView, boolean isTopTaskHome)661     void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
662             TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
663         preloadIcon(topTask);
664 
665         // Update the destination rect
666         mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
667         final Task toTask = new Task();
668         final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
669                 topTask.id, toTask);
670         new AsyncTask<Void, Void, Bitmap>() {
671             @Override
672             protected Bitmap doInBackground(Void... params) {
673                 return drawThumbnailTransitionBitmap(toTask, toTransform);
674             }
675 
676             @Override
677             protected void onPostExecute(Bitmap bitmap) {
678                 mThumbnailTransitionBitmapCache = bitmap;
679                 mThumbnailTransitionBitmapCacheKey = toTask;
680             }
681         }.execute();
682     }
683 
684     /**
685      * Draws the header of a task used for the window animation into a bitmap.
686      */
drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform)687     Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
688         if (toTransform != null && toTask.key != null) {
689             Bitmap thumbnail;
690             synchronized (mHeaderBarLock) {
691                 int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
692                 int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
693                 thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
694                         Bitmap.Config.ARGB_8888);
695                 if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
696                     thumbnail.eraseColor(0xFFff0000);
697                 } else {
698                     Canvas c = new Canvas(thumbnail);
699                     c.scale(toTransform.scale, toTransform.scale);
700                     mHeaderBar.rebindToTask(toTask);
701                     mHeaderBar.draw(c);
702                     c.setBitmap(null);
703                 }
704             }
705             return thumbnail.createAshmemBitmap();
706         }
707         return null;
708     }
709 
710     /** Returns the transition rect for the given task id. */
getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, int runningTaskId, Task runningTaskOut)711     TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
712             int runningTaskId, Task runningTaskOut) {
713         // Find the running task in the TaskStack
714         Task task = null;
715         ArrayList<Task> tasks = stack.getTasks();
716         if (runningTaskId != -1) {
717             // Otherwise, try and find the task with the
718             int taskCount = tasks.size();
719             for (int i = taskCount - 1; i >= 0; i--) {
720                 Task t = tasks.get(i);
721                 if (t.key.id == runningTaskId) {
722                     task = t;
723                     runningTaskOut.copyFrom(t);
724                     break;
725                 }
726             }
727         }
728         if (task == null) {
729             // If no task is specified or we can not find the task just use the front most one
730             task = tasks.get(tasks.size() - 1);
731             runningTaskOut.copyFrom(task);
732         }
733 
734         // Get the transform for the running task
735         stackView.getScroller().setStackScrollToInitialState();
736         mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
737                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
738         return mTmpTransform;
739     }
740 
741     /** Starts the recents activity */
startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome)742     void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
743         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
744         RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
745 
746         if (sInstanceLoadPlan == null) {
747             // Create a new load plan if onPreloadRecents() was never triggered
748             sInstanceLoadPlan = loader.createLoadPlan(mContext);
749         }
750 
751         // Temporarily skip the transition (use a dummy fade) if multi stack is enabled.
752         // For multi-stack we need to figure out where each of the tasks are going.
753         if (mConfig.multiStackEnabled) {
754             loader.preloadTasks(sInstanceLoadPlan, true);
755             ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
756             TaskStack stack = stacks.get(0);
757             mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true);
758             TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
759                     mDummyStackView.computeStackVisibilityReport();
760             ActivityOptions opts = getUnknownTransitionActivityOptions();
761             startAlternateRecentsActivity(topTask, opts, true /* fromHome */,
762                     false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
763             return;
764         }
765 
766         if (!sInstanceLoadPlan.hasTasks()) {
767             loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
768         }
769         ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
770         TaskStack stack = stacks.get(0);
771 
772         // Prepare the dummy stack for the transition
773         mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
774         TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
775                 mDummyStackView.computeStackVisibilityReport();
776         boolean hasRecentTasks = stack.getTaskCount() > 0;
777         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
778 
779         if (useThumbnailTransition) {
780 
781             // Try starting with a thumbnail transition
782             ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
783                     mDummyStackView);
784             if (opts != null) {
785                 startAlternateRecentsActivity(topTask, opts, false /* fromHome */,
786                         false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
787             } else {
788                 // Fall through below to the non-thumbnail transition
789                 useThumbnailTransition = false;
790             }
791         }
792 
793         if (!useThumbnailTransition) {
794             // If there is no thumbnail transition, but is launching from home into recents, then
795             // use a quick home transition and do the animation from home
796             if (hasRecentTasks) {
797                 String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
798                 String searchWidgetPackage =
799                         Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
800 
801                 // Determine whether we are coming from a search owned home activity
802                 boolean fromSearchHome = (homeActivityPackage != null) &&
803                         homeActivityPackage.equals(searchWidgetPackage);
804                 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
805                 startAlternateRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
806                         false /* fromThumbnail */, stackVr);
807             } else {
808                 // Otherwise we do the normal fade from an unknown source
809                 ActivityOptions opts = getUnknownTransitionActivityOptions();
810                 startAlternateRecentsActivity(topTask, opts, true /* fromHome */,
811                         false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
812             }
813         }
814         mLastToggleTime = SystemClock.elapsedRealtime();
815     }
816 
817     /** Starts the recents activity */
startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, TaskStackViewLayoutAlgorithm.VisibilityReport vr)818     void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
819             ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
820             TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
821         // Update the configuration based on the launch options
822         mConfig.launchedFromHome = fromSearchHome || fromHome;
823         mConfig.launchedFromSearchHome = fromSearchHome;
824         mConfig.launchedFromAppWithThumbnail = fromThumbnail;
825         mConfig.launchedToTaskId = (topTask != null) ? topTask.id : -1;
826         mConfig.launchedWithAltTab = mTriggeredFromAltTab;
827         mConfig.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
828         mConfig.launchedNumVisibleTasks = vr.numVisibleTasks;
829         mConfig.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
830         mConfig.launchedHasConfigurationChanged = false;
831 
832         Intent intent = new Intent(sToggleRecentsAction);
833         intent.setClassName(sRecentsPackage, sRecentsActivity);
834         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
835                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
836                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
837         if (opts != null) {
838             mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
839         } else {
840             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
841         }
842         mCanReuseTaskStackViews = true;
843     }
844 
845     /** Sets the RecentsComponent callbacks. */
846     @Override
setCallback(RecentsComponent.Callbacks cb)847     public void setCallback(RecentsComponent.Callbacks cb) {
848         sRecentsComponentCallbacks = cb;
849     }
850 
851     /** Notifies the callbacks that the visibility of Recents has changed. */
852     @ProxyFromAnyToPrimaryUser
notifyVisibilityChanged(Context context, SystemServicesProxy ssp, boolean visible)853     public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
854             boolean visible) {
855         if (ssp.isForegroundUserOwner()) {
856             visibilityChanged(visible);
857         } else {
858             Intent intent = createLocalBroadcastIntent(context,
859                     ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
860             intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
861             context.sendBroadcastAsUser(intent, UserHandle.OWNER);
862         }
863     }
visibilityChanged(boolean visible)864     static void visibilityChanged(boolean visible) {
865         if (sRecentsComponentCallbacks != null) {
866             sRecentsComponentCallbacks.onVisibilityChanged(visible);
867         }
868     }
869 
870     /** Notifies the status bar to trigger screen pinning. */
871     @ProxyFromAnyToPrimaryUser
startScreenPinning(Context context, SystemServicesProxy ssp)872     public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
873         if (ssp.isForegroundUserOwner()) {
874             onStartScreenPinning(context);
875         } else {
876             Intent intent = createLocalBroadcastIntent(context,
877                     ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
878             context.sendBroadcastAsUser(intent, UserHandle.OWNER);
879         }
880     }
onStartScreenPinning(Context context)881     static void onStartScreenPinning(Context context) {
882         // For the primary user, the context for the SystemUI component is the SystemUIApplication
883         SystemUIApplication app = (SystemUIApplication)
884                 getInstanceAndStartIfNeeded(context).mContext;
885         PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
886         if (statusBar != null) {
887             statusBar.showScreenPinningRequest(false);
888         }
889     }
890 
891     /**
892      * Returns the preloaded load plan and invalidates it.
893      */
consumeInstanceLoadPlan()894     public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
895         RecentsTaskLoadPlan plan = sInstanceLoadPlan;
896         sInstanceLoadPlan = null;
897         return plan;
898     }
899 
900     /**** OnAnimationStartedListener Implementation ****/
901 
902     @Override
onAnimationStarted()903     public void onAnimationStarted() {
904         // Notify recents to start the enter animation
905         if (!mStartAnimationTriggered) {
906             // There can be a race condition between the start animation callback and
907             // the start of the new activity (where we register the receiver that listens
908             // to this broadcast, so we add our own receiver and if that gets called, then
909             // we know the activity has not yet started and we can retry sending the broadcast.
910             BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
911                 @Override
912                 public void onReceive(Context context, Intent intent) {
913                     if (getResultCode() == Activity.RESULT_OK) {
914                         mStartAnimationTriggered = true;
915                         return;
916                     }
917 
918                     // Schedule for the broadcast to be sent again after some time
919                     mHandler.postDelayed(new Runnable() {
920                         @Override
921                         public void run() {
922                             onAnimationStarted();
923                         }
924                     }, 25);
925                 }
926             };
927 
928             // Send the broadcast to notify Recents that the animation has started
929             Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
930             mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
931                     fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
932         }
933     }
934 }
935