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