• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.appwidget.AppWidgetHost;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.ActivityNotFoundException;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
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.Handler;
35 import android.os.UserHandle;
36 import android.util.Pair;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import com.android.systemui.R;
40 import com.android.systemui.RecentsComponent;
41 import com.android.systemui.recents.misc.Console;
42 import com.android.systemui.recents.misc.SystemServicesProxy;
43 import com.android.systemui.recents.model.RecentsTaskLoader;
44 import com.android.systemui.recents.model.Task;
45 import com.android.systemui.recents.model.TaskGrouping;
46 import com.android.systemui.recents.model.TaskStack;
47 import com.android.systemui.recents.views.TaskStackView;
48 import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
49 import com.android.systemui.recents.views.TaskViewHeader;
50 import com.android.systemui.recents.views.TaskViewTransform;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.concurrent.atomic.AtomicBoolean;
55 
56 /** A proxy implementation for the recents component */
57 public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
58 
59     final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
60     final public static String EXTRA_FROM_SEARCH_HOME = "recents.triggeredOverSearchHome";
61     final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
62     final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
63     final public static String EXTRA_FROM_TASK_ID = "recents.activeTaskId";
64     final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
65     final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
66 
67     final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
68     final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
69     final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
70 
71     final static int sMinToggleDelay = 350;
72 
73     final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
74     final static String sRecentsPackage = "com.android.systemui";
75     final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
76 
77     static Bitmap sLastScreenshot;
78     static RecentsComponent.Callbacks sRecentsComponentCallbacks;
79 
80     Context mContext;
81     LayoutInflater mInflater;
82     SystemServicesProxy mSystemServicesProxy;
83     Handler mHandler;
84     boolean mBootCompleted;
85     boolean mStartAnimationTriggered;
86 
87     // Task launching
88     RecentsConfiguration mConfig;
89     Rect mWindowRect = new Rect();
90     Rect mTaskStackBounds = new Rect();
91     Rect mSystemInsets = new Rect();
92     TaskViewTransform mTmpTransform = new TaskViewTransform();
93     int mStatusBarHeight;
94     int mNavBarHeight;
95     int mNavBarWidth;
96 
97     // Header (for transition)
98     TaskViewHeader mHeaderBar;
99     TaskStackView mDummyStackView;
100 
101     // Variables to keep track of if we need to start recents after binding
102     View mStatusBarView;
103     boolean mTriggeredFromAltTab;
104     long mLastToggleTime;
105 
AlternateRecentsComponent(Context context)106     public AlternateRecentsComponent(Context context) {
107         RecentsTaskLoader.initialize(context);
108         mInflater = LayoutInflater.from(context);
109         mContext = context;
110         mSystemServicesProxy = new SystemServicesProxy(context);
111         mHandler = new Handler();
112         mTaskStackBounds = new Rect();
113     }
114 
onStart()115     public void onStart() {
116         // Initialize some static datastructures
117         TaskStackViewLayoutAlgorithm.initializeCurve();
118         // Load the header bar layout
119         reloadHeaderBarLayout();
120         // Try and pre-emptively bind the search widget on startup to ensure that we
121         // have the right thumbnail bounds to animate to.
122         if (Constants.DebugFlags.App.EnableSearchLayout) {
123             // If there is no id, then bind a new search app widget
124             if (mConfig.searchBarAppWidgetId < 0) {
125                 AppWidgetHost host = new RecentsAppWidgetHost(mContext,
126                         Constants.Values.App.AppWidgetHostId);
127                 Pair<Integer, AppWidgetProviderInfo> widgetInfo =
128                         mSystemServicesProxy.bindSearchAppWidget(host);
129                 if (widgetInfo != null) {
130                     // Save the app widget id into the settings
131                     mConfig.updateSearchBarAppWidgetId(mContext, widgetInfo.first);
132                 }
133             }
134         }
135     }
136 
onBootCompleted()137     public void onBootCompleted() {
138         mBootCompleted = true;
139     }
140 
141     /** Shows the recents */
onShowRecents(boolean triggeredFromAltTab, View statusBarView)142     public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
143         mStatusBarView = statusBarView;
144         mTriggeredFromAltTab = triggeredFromAltTab;
145 
146         try {
147             startRecentsActivity();
148         } catch (ActivityNotFoundException e) {
149             Console.logRawError("Failed to launch RecentAppsIntent", e);
150         }
151     }
152 
153     /** Hides the recents */
onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)154     public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
155         if (mBootCompleted) {
156             if (isRecentsTopMost(getTopMostTask(), null)) {
157                 // Notify recents to hide itself
158                 Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY);
159                 intent.setPackage(mContext.getPackageName());
160                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
161                         Intent.FLAG_RECEIVER_FOREGROUND);
162                 intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
163                 intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
164                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
165             }
166         }
167     }
168 
169     /** Toggles the alternate recents activity */
onToggleRecents(View statusBarView)170     public void onToggleRecents(View statusBarView) {
171         mStatusBarView = statusBarView;
172         mTriggeredFromAltTab = false;
173 
174         try {
175             toggleRecentsActivity();
176         } catch (ActivityNotFoundException e) {
177             Console.logRawError("Failed to launch RecentAppsIntent", e);
178         }
179     }
180 
onPreloadRecents()181     public void onPreloadRecents() {
182         // Do nothing
183     }
184 
onCancelPreloadingRecents()185     public void onCancelPreloadingRecents() {
186         // Do nothing
187     }
188 
showRelativeAffiliatedTask(boolean showNextTask)189     void showRelativeAffiliatedTask(boolean showNextTask) {
190         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
191         TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
192                 -1, -1, false, true, null, null);
193         // Return early if there are no tasks
194         if (stack.getTaskCount() == 0) return;
195 
196         ActivityManager.RunningTaskInfo runningTask = getTopMostTask();
197         // Return early if the running task is in the home stack (optimization)
198         if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
199 
200         // Find the task in the recents list
201         ArrayList<Task> tasks = stack.getTasks();
202         Task toTask = null;
203         ActivityOptions launchOpts = null;
204         int taskCount = tasks.size();
205         for (int i = 0; i < taskCount; i++) {
206             Task task = tasks.get(i);
207             if (task.key.id == runningTask.id) {
208                 TaskGrouping group = task.group;
209                 Task.TaskKey toTaskKey;
210                 if (showNextTask) {
211                     toTaskKey = group.getNextTaskInGroup(task);
212                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
213                             R.anim.recents_launch_next_affiliated_task_target,
214                             R.anim.recents_launch_next_affiliated_task_source);
215                 } else {
216                     toTaskKey = group.getPrevTaskInGroup(task);
217                     launchOpts = ActivityOptions.makeCustomAnimation(mContext,
218                             R.anim.recents_launch_prev_affiliated_task_target,
219                             R.anim.recents_launch_prev_affiliated_task_source);
220                 }
221                 if (toTaskKey != null) {
222                     toTask = stack.findTaskWithId(toTaskKey.id);
223                 }
224                 break;
225             }
226         }
227 
228         // Return early if there is no next task
229         if (toTask == null) {
230             if (showNextTask) {
231                 // XXX: Show the next-task bounce animation
232             } else {
233                 // XXX: Show the prev-task bounce animation
234             }
235             return;
236         }
237 
238         // Launch the task
239         if (toTask.isActive) {
240             // Bring an active task to the foreground
241             mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
242         } else {
243             mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
244                     toTask.activityLabel, launchOpts);
245         }
246     }
247 
onShowNextAffiliatedTask()248     public void onShowNextAffiliatedTask() {
249         showRelativeAffiliatedTask(true);
250     }
251 
onShowPrevAffiliatedTask()252     public void onShowPrevAffiliatedTask() {
253         showRelativeAffiliatedTask(false);
254     }
255 
onConfigurationChanged(Configuration newConfig)256     public void onConfigurationChanged(Configuration newConfig) {
257         // Reload the header bar layout
258         reloadHeaderBarLayout();
259         sLastScreenshot = null;
260     }
261 
262     /** Prepares the header bar layout. */
reloadHeaderBarLayout()263     void reloadHeaderBarLayout() {
264         Resources res = mContext.getResources();
265         mWindowRect = mSystemServicesProxy.getWindowRect();
266         mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
267         mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
268         mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
269         mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
270         mConfig.updateOnConfigurationChange();
271         mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
272                 (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds);
273         if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
274             mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
275         } else {
276             mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
277         }
278 
279         // Inflate the header bar layout so that we can rebind and draw it for the transition
280         TaskStack stack = new TaskStack();
281         mDummyStackView = new TaskStackView(mContext, stack);
282         TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
283         Rect taskStackBounds = new Rect(mTaskStackBounds);
284         taskStackBounds.bottom -= mSystemInsets.bottom;
285         algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
286         Rect taskViewSize = algo.getUntransformedTaskViewSize();
287         int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
288         mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
289                 false);
290         mHeaderBar.measure(
291                 View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
292                 View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
293         mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
294     }
295 
296     /** Gets the top task. */
getTopMostTask()297     ActivityManager.RunningTaskInfo getTopMostTask() {
298         SystemServicesProxy ssp = mSystemServicesProxy;
299         List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1);
300         if (!tasks.isEmpty()) {
301             return tasks.get(0);
302         }
303         return null;
304     }
305 
306     /** Returns whether the recents is currently running */
isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost)307     boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) {
308         SystemServicesProxy ssp = mSystemServicesProxy;
309         if (topTask != null) {
310             ComponentName topActivity = topTask.topActivity;
311 
312             // Check if the front most activity is recents
313             if (topActivity.getPackageName().equals(sRecentsPackage) &&
314                     topActivity.getClassName().equals(sRecentsActivity)) {
315                 if (isHomeTopMost != null) {
316                     isHomeTopMost.set(false);
317                 }
318                 return true;
319             }
320 
321             if (isHomeTopMost != null) {
322                 isHomeTopMost.set(ssp.isInHomeStack(topTask.id));
323             }
324         }
325         return false;
326     }
327 
328     /** Toggles the recents activity */
toggleRecentsActivity()329     void toggleRecentsActivity() {
330         // If the user has toggled it too quickly, then just eat up the event here (it's better than
331         // showing a janky screenshot).
332         // NOTE: Ideally, the screenshot mechanism would take the window transform into account
333         if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) {
334             return;
335         }
336 
337         // If Recents is the front most activity, then we should just communicate with it directly
338         // to launch the first task or dismiss itself
339         ActivityManager.RunningTaskInfo topTask = getTopMostTask();
340         AtomicBoolean isTopTaskHome = new AtomicBoolean();
341         if (isRecentsTopMost(topTask, isTopTaskHome)) {
342             // Notify recents to toggle itself
343             Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
344             intent.setPackage(mContext.getPackageName());
345             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
346                     Intent.FLAG_RECEIVER_FOREGROUND);
347             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
348             mLastToggleTime = System.currentTimeMillis();
349             return;
350         } else {
351             // Otherwise, start the recents activity
352             startRecentsActivity(topTask, isTopTaskHome.get());
353         }
354     }
355 
356     /** Starts the recents activity if it is not already running */
startRecentsActivity()357     void startRecentsActivity() {
358         // Check if the top task is in the home stack, and start the recents activity
359         ActivityManager.RunningTaskInfo topTask = getTopMostTask();
360         AtomicBoolean isTopTaskHome = new AtomicBoolean();
361         if (!isRecentsTopMost(topTask, isTopTaskHome)) {
362             startRecentsActivity(topTask, isTopTaskHome.get());
363         }
364     }
365 
366     /**
367      * Creates the activity options for a unknown state->recents transition.
368      */
getUnknownTransitionActivityOptions()369     ActivityOptions getUnknownTransitionActivityOptions() {
370         mStartAnimationTriggered = false;
371         return ActivityOptions.makeCustomAnimation(mContext,
372                 R.anim.recents_from_unknown_enter,
373                 R.anim.recents_from_unknown_exit, mHandler, this);
374     }
375 
376     /**
377      * Creates the activity options for a home->recents transition.
378      */
getHomeTransitionActivityOptions(boolean fromSearchHome)379     ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
380         mStartAnimationTriggered = false;
381         if (fromSearchHome) {
382             return ActivityOptions.makeCustomAnimation(mContext,
383                     R.anim.recents_from_search_launcher_enter,
384                     R.anim.recents_from_search_launcher_exit, mHandler, this);
385         }
386         return ActivityOptions.makeCustomAnimation(mContext,
387                 R.anim.recents_from_launcher_enter,
388                 R.anim.recents_from_launcher_exit, mHandler, this);
389     }
390 
391     /**
392      * Creates the activity options for an app->recents transition.  If this method sets the static
393      * screenshot, then we will use that for the transition.
394      */
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome)395     ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
396             boolean isTopTaskHome) {
397         if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
398             // Recycle the last screenshot
399             consumeLastScreenshot();
400 
401             // Take the full screenshot
402             sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
403             if (sLastScreenshot != null) {
404                 mStartAnimationTriggered = false;
405                 return ActivityOptions.makeCustomAnimation(mContext,
406                         R.anim.recents_from_app_enter,
407                         R.anim.recents_from_app_exit, mHandler, this);
408             }
409         }
410 
411         // Update the destination rect
412         Task toTask = new Task();
413         TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome,
414                 toTask);
415         if (toTransform != null && toTask.key != null) {
416             Rect toTaskRect = toTransform.rect;
417             int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
418             int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
419             Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
420                     Bitmap.Config.ARGB_8888);
421             if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
422                 thumbnail.eraseColor(0xFFff0000);
423             } else {
424                 Canvas c = new Canvas(thumbnail);
425                 c.scale(toTransform.scale, toTransform.scale);
426                 mHeaderBar.rebindToTask(toTask);
427                 mHeaderBar.draw(c);
428                 c.setBitmap(null);
429             }
430 
431             mStartAnimationTriggered = false;
432             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView,
433                     thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
434                     toTaskRect.height(), this);
435         }
436 
437         // If both the screenshot and thumbnail fails, then just fall back to the default transition
438         return getUnknownTransitionActivityOptions();
439     }
440 
441     /** Returns the transition rect for the given task id. */
getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, Task runningTaskOut)442     TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome,
443                                                       Task runningTaskOut) {
444         // Get the stack of tasks that we are animating into
445         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
446         TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
447                 runningTaskId, -1, false, isTopTaskHome, null, null);
448         if (stack.getTaskCount() == 0) {
449             return null;
450         }
451 
452         // Find the running task in the TaskStack
453         Task task = null;
454         ArrayList<Task> tasks = stack.getTasks();
455         if (runningTaskId != -1) {
456             // Otherwise, try and find the task with the
457             int taskCount = tasks.size();
458             for (int i = taskCount - 1; i >= 0; i--) {
459                 Task t = tasks.get(i);
460                 if (t.key.id == runningTaskId) {
461                     task = t;
462                     runningTaskOut.copyFrom(t);
463                     break;
464                 }
465             }
466         }
467         if (task == null) {
468             // If no task is specified or we can not find the task just use the front most one
469             task = tasks.get(tasks.size() - 1);
470         }
471 
472         // Get the transform for the running task
473         mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
474         mDummyStackView.getScroller().setStackScrollToInitialState();
475         mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task,
476                 mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null);
477         return mTmpTransform;
478     }
479 
480     /** Starts the recents activity */
startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome)481     void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
482         // If Recents is not the front-most activity and we should animate into it.  If
483         // the activity at the root of the top task stack in the home stack, then we just do a
484         // simple transition.  Otherwise, we animate to the rects defined by the Recents service,
485         // which can differ depending on the number of items in the list.
486         SystemServicesProxy ssp = mSystemServicesProxy;
487         List<ActivityManager.RecentTaskInfo> recentTasks =
488                 ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
489         boolean useThumbnailTransition = !isTopTaskHome;
490         boolean hasRecentTasks = !recentTasks.isEmpty();
491 
492         if (useThumbnailTransition) {
493             // Try starting with a thumbnail transition
494             ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome);
495             if (opts != null) {
496                 if (sLastScreenshot != null) {
497                     startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
498                 } else {
499                     startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL);
500                 }
501             } else {
502                 // Fall through below to the non-thumbnail transition
503                 useThumbnailTransition = false;
504             }
505         }
506 
507         if (!useThumbnailTransition) {
508             // If there is no thumbnail transition, but is launching from home into recents, then
509             // use a quick home transition and do the animation from home
510             if (hasRecentTasks) {
511                 // Get the home activity info
512                 String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
513                 // Get the search widget info
514                 AppWidgetProviderInfo searchWidget = null;
515                 String searchWidgetPackage = null;
516                 if (mConfig.hasSearchBarAppWidget()) {
517                     searchWidget = mSystemServicesProxy.getAppWidgetInfo(
518                             mConfig.searchBarAppWidgetId);
519                 } else {
520                     searchWidget = mSystemServicesProxy.resolveSearchAppWidget();
521                 }
522                 if (searchWidget != null && searchWidget.provider != null) {
523                     searchWidgetPackage = searchWidget.provider.getPackageName();
524                 }
525                 // Determine whether we are coming from a search owned home activity
526                 boolean fromSearchHome = false;
527                 if (homeActivityPackage != null && searchWidgetPackage != null &&
528                         homeActivityPackage.equals(searchWidgetPackage)) {
529                     fromSearchHome = true;
530                 }
531 
532                 ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
533                 startAlternateRecentsActivity(topTask, opts,
534                         fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME);
535             } else {
536                 // Otherwise we do the normal fade from an unknown source
537                 ActivityOptions opts = getUnknownTransitionActivityOptions();
538                 startAlternateRecentsActivity(topTask, opts, null);
539             }
540         }
541         mLastToggleTime = System.currentTimeMillis();
542     }
543 
544     /** Starts the recents activity */
startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, ActivityOptions opts, String extraFlag)545     void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
546                                        ActivityOptions opts, String extraFlag) {
547         Intent intent = new Intent(sToggleRecentsAction);
548         intent.setClassName(sRecentsPackage, sRecentsActivity);
549         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
550                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
551                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
552         if (extraFlag != null) {
553             intent.putExtra(extraFlag, true);
554         }
555         intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
556         intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
557         if (opts != null) {
558             mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
559         } else {
560             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
561         }
562     }
563 
564     /** Returns the last screenshot taken, this will be called by the RecentsActivity. */
getLastScreenshot()565     public static Bitmap getLastScreenshot() {
566         return sLastScreenshot;
567     }
568 
569     /** Recycles the last screenshot taken, this will be called by the RecentsActivity. */
consumeLastScreenshot()570     public static void consumeLastScreenshot() {
571         if (sLastScreenshot != null) {
572             sLastScreenshot.recycle();
573             sLastScreenshot = null;
574         }
575     }
576 
577     /** Sets the RecentsComponent callbacks. */
setRecentsComponentCallback(RecentsComponent.Callbacks cb)578     public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) {
579         sRecentsComponentCallbacks = cb;
580     }
581 
582     /** Notifies the callbacks that the visibility of Recents has changed. */
notifyVisibilityChanged(boolean visible)583     public static void notifyVisibilityChanged(boolean visible) {
584         if (sRecentsComponentCallbacks != null) {
585             sRecentsComponentCallbacks.onVisibilityChanged(visible);
586         }
587     }
588 
589     /**** OnAnimationStartedListener Implementation ****/
590 
591     @Override
onAnimationStarted()592     public void onAnimationStarted() {
593         // Notify recents to start the enter animation
594         if (!mStartAnimationTriggered) {
595             // There can be a race condition between the start animation callback and
596             // the start of the new activity (where we register the receiver that listens
597             // to this broadcast, so we add our own receiver and if that gets called, then
598             // we know the activity has not yet started and we can retry sending the broadcast.
599             BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
600                 @Override
601                 public void onReceive(Context context, Intent intent) {
602                     if (getResultCode() == Activity.RESULT_OK) {
603                         mStartAnimationTriggered = true;
604                         return;
605                     }
606 
607                     // Schedule for the broadcast to be sent again after some time
608                     mHandler.postDelayed(new Runnable() {
609                         @Override
610                         public void run() {
611                             onAnimationStarted();
612                         }
613                     }, 75);
614                 }
615             };
616 
617             // Send the broadcast to notify Recents that the animation has started
618             Intent intent = new Intent(ACTION_START_ENTER_ANIMATION);
619             intent.setPackage(mContext.getPackageName());
620             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
621                     Intent.FLAG_RECEIVER_FOREGROUND);
622             mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
623                     fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
624         }
625     }
626 }
627