• 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 static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
20 
21 import android.app.Activity;
22 import android.app.ActivityOptions;
23 import android.app.TaskStackBuilder;
24 import android.app.WallpaperManager;
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.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.SystemClock;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.View;
40 import android.view.ViewTreeObserver;
41 import android.view.ViewTreeObserver.OnPreDrawListener;
42 import android.view.WindowManager;
43 import android.view.WindowManager.LayoutParams;
44 
45 import com.android.internal.colorextraction.ColorExtractor;
46 import com.android.internal.content.PackageMonitor;
47 import com.android.internal.logging.MetricsLogger;
48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
49 import com.android.internal.util.LatencyTracker;
50 import com.android.systemui.DejankUtils;
51 import com.android.systemui.Dependency;
52 import com.android.systemui.Interpolators;
53 import com.android.systemui.R;
54 import com.android.systemui.colorextraction.SysuiColorExtractor;
55 import com.android.systemui.recents.events.EventBus;
56 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
57 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
58 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
59 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
60 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
61 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
62 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
63 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
64 import com.android.systemui.recents.events.activity.HideRecentsEvent;
65 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
66 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
67 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
68 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
69 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
70 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
71 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
72 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
73 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
74 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
75 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
76 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
77 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
78 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
79 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
80 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
81 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
82 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
83 import com.android.systemui.recents.events.ui.UserInteractionEvent;
84 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
85 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
86 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
87 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
88 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
89 import com.android.systemui.recents.misc.SystemServicesProxy;
90 import com.android.systemui.shared.recents.utilities.Utilities;
91 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
92 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
93 import com.android.systemui.shared.recents.model.Task;
94 import com.android.systemui.shared.recents.model.TaskStack;
95 import com.android.systemui.recents.views.RecentsView;
96 import com.android.systemui.recents.views.SystemBarScrimViews;
97 import com.android.systemui.shared.system.ActivityManagerWrapper;
98 
99 import com.android.systemui.shared.system.WindowManagerWrapper;
100 import java.io.FileDescriptor;
101 import java.io.PrintWriter;
102 
103 /**
104  * The main Recents activity that is started from RecentsComponent.
105  */
106 public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener,
107         ColorExtractor.OnColorsChangedListener {
108 
109     private final static String TAG = "RecentsActivity";
110     private final static boolean DEBUG = false;
111 
112     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
113     public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
114 
115     private PackageMonitor mPackageMonitor = new PackageMonitor() {
116             @Override
117             public void onPackageRemoved(String packageName, int uid) {
118                 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
119             }
120 
121             @Override
122             public boolean onPackageChanged(String packageName, int uid, String[] components) {
123                 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
124                 return true;
125             }
126 
127             @Override
128             public void onPackageModified(String packageName) {
129                 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
130             }
131         };
132     private Handler mHandler = new Handler();
133     private long mLastTabKeyEventTime;
134     private boolean mFinishedOnStartup;
135     private boolean mIgnoreAltTabRelease;
136     private boolean mIsVisible;
137     private boolean mRecentsStartRequested;
138     private Configuration mLastConfig;
139 
140     // Top level views
141     private RecentsView mRecentsView;
142     private SystemBarScrimViews mScrimViews;
143     private View mIncompatibleAppOverlay;
144 
145     // Runnables to finish the Recents activity
146     private Intent mHomeIntent;
147 
148     // The trigger to automatically launch the current task
149     private int mFocusTimerDuration;
150     private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
151 
152     // Theme and colors
153     private SysuiColorExtractor mColorExtractor;
154     private boolean mUsingDarkText;
155 
156     /**
157      * A common Runnable to finish Recents by launching Home with an animation depending on the
158      * last activity launch state. Generally we always launch home when we exit Recents rather than
159      * just finishing the activity since we don't know what is behind Recents in the task stack.
160      */
161     class LaunchHomeRunnable implements Runnable {
162 
163         Intent mLaunchIntent;
164         ActivityOptions mOpts;
165 
166         /**
167          * Creates a finish runnable that starts the specified intent.
168          */
LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts)169         public LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts) {
170             mLaunchIntent = launchIntent;
171             mOpts = opts;
172         }
173 
174         @Override
run()175         public void run() {
176             try {
177                 mHandler.post(() -> {
178                     ActivityOptions opts = mOpts;
179                     if (opts == null) {
180                         opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
181                                 R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit);
182                     }
183                     startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
184                 });
185             } catch (Exception e) {
186                 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
187             }
188         }
189     }
190 
191     /**
192      * Broadcast receiver to handle messages from the system
193      */
194     final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
195         @Override
196         public void onReceive(Context ctx, Intent intent) {
197             String action = intent.getAction();
198             if (action.equals(Intent.ACTION_SCREEN_OFF)) {
199                 // When the screen turns off, dismiss Recents to Home
200                 dismissRecentsToHomeIfVisible(false);
201             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
202                 // When switching users, dismiss Recents to Home similar to screen off
203                 finish();
204             }
205         }
206     };
207 
208     private final OnPreDrawListener mRecentsDrawnEventListener =
209             new ViewTreeObserver.OnPreDrawListener() {
210                 @Override
211                 public boolean onPreDraw() {
212                     mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
213                     EventBus.getDefault().post(new RecentsDrawnEvent());
214                     if (LatencyTracker.isEnabled(getApplicationContext())) {
215                         DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(
216                                 getApplicationContext()).onActionEnd(
217                                 LatencyTracker.ACTION_TOGGLE_RECENTS));
218                     }
219                     DejankUtils.postAfterTraversal(() -> {
220                         Recents.getTaskLoader().startLoader(RecentsActivity.this);
221                         Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
222                     });
223                     return true;
224                 }
225             };
226 
227     /**
228      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
229      */
dismissRecentsToFocusedTask(int logCategory)230     boolean dismissRecentsToFocusedTask(int logCategory) {
231         SystemServicesProxy ssp = Recents.getSystemServices();
232         if (ssp.isRecentsActivityVisible()) {
233             // If we have a focused Task, launch that Task now
234             if (mRecentsView.launchFocusedTask(logCategory)) return true;
235         }
236         return false;
237     }
238 
239     /**
240      * Dismisses recents back to the launch target task.
241      */
dismissRecentsToLaunchTargetTaskOrHome()242     boolean dismissRecentsToLaunchTargetTaskOrHome() {
243         SystemServicesProxy ssp = Recents.getSystemServices();
244         if (ssp.isRecentsActivityVisible()) {
245             // If we have a focused Task, launch that Task now
246             if (mRecentsView.launchPreviousTask()) return true;
247             // If none of the other cases apply, then just go Home
248             dismissRecentsToHome(true /* animateTaskViews */);
249         }
250         return false;
251     }
252 
253     /**
254      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
255      */
dismissRecentsToFocusedTaskOrHome()256     boolean dismissRecentsToFocusedTaskOrHome() {
257         SystemServicesProxy ssp = Recents.getSystemServices();
258         if (ssp.isRecentsActivityVisible()) {
259             // If we have a focused Task, launch that Task now
260             if (mRecentsView.launchFocusedTask(0 /* logCategory */)) return true;
261             // If none of the other cases apply, then just go Home
262             dismissRecentsToHome(true /* animateTaskViews */);
263             return true;
264         }
265         return false;
266     }
267 
268     /**
269      * Dismisses Recents directly to Home without checking whether it is currently visible.
270      */
dismissRecentsToHome(boolean animateTaskViews)271     void dismissRecentsToHome(boolean animateTaskViews) {
272         dismissRecentsToHome(animateTaskViews, null);
273     }
274 
275     /**
276      * Dismisses Recents directly to Home without checking whether it is currently visible.
277      *
278      * @param overrideAnimation If not null, will override the default animation that is based on
279      *                          how Recents was launched.
280      */
dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation)281     void dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation) {
282         DismissRecentsToHomeAnimationStarted dismissEvent =
283                 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
284         dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
285                 overrideAnimation));
286         ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
287         EventBus.getDefault().send(dismissEvent);
288     }
289 
290     /** Dismisses Recents directly to Home if we currently aren't transitioning. */
dismissRecentsToHomeIfVisible(boolean animated)291     boolean dismissRecentsToHomeIfVisible(boolean animated) {
292         SystemServicesProxy ssp = Recents.getSystemServices();
293         if (ssp.isRecentsActivityVisible()) {
294             // Return to Home
295             dismissRecentsToHome(animated);
296             return true;
297         }
298         return false;
299     }
300 
301     /** Called with the activity is first created. */
302     @Override
onCreate(Bundle savedInstanceState)303     public void onCreate(Bundle savedInstanceState) {
304         super.onCreate(savedInstanceState);
305         mFinishedOnStartup = false;
306 
307         // In the case that the activity starts up before the Recents component has initialized
308         // (usually when debugging/pushing the SysUI apk), just finish this activity.
309         SystemServicesProxy ssp = Recents.getSystemServices();
310         if (ssp == null) {
311             mFinishedOnStartup = true;
312             finish();
313             return;
314         }
315 
316         // Register this activity with the event bus
317         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
318 
319         // Initialize the package monitor
320         mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
321                 true /* externalStorage */);
322 
323         // Select theme based on wallpaper colors
324         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
325         mColorExtractor.addOnColorsChangedListener(this);
326         mUsingDarkText = mColorExtractor.getColors(ColorExtractor.TYPE_DARK,
327                 WallpaperManager.FLAG_SYSTEM, true).supportsDarkText();
328         setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light
329                 : R.style.RecentsTheme_Wallpaper);
330 
331         // Set the Recents layout
332         setContentView(R.layout.recents);
333         takeKeyEvents(true);
334         mRecentsView = findViewById(R.id.recents_view);
335         mScrimViews = new SystemBarScrimViews(this);
336         getWindow().getAttributes().privateFlags |=
337                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
338         if (Recents.getConfiguration().isLowRamDevice) {
339             getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
340         }
341 
342         mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
343 
344         // Set the window background
345         mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
346 
347         // Create the home intent runnable
348         mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
349         mHomeIntent.addCategory(Intent.CATEGORY_HOME);
350         mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
351                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
352 
353         // Register the broadcast receiver to handle messages when the screen is turned off
354         IntentFilter filter = new IntentFilter();
355         filter.addAction(Intent.ACTION_SCREEN_OFF);
356         filter.addAction(Intent.ACTION_USER_SWITCHED);
357         registerReceiver(mSystemBroadcastReceiver, filter);
358 
359         getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
360     }
361 
362     @Override
onStart()363     protected void onStart() {
364         super.onStart();
365 
366         // Reload the stack view whenever we are made visible again
367         reloadStackView();
368 
369         // Notify that recents is now visible
370         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
371         MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY);
372 
373         // Getting system scrim colors ignoring wallpaper visibility since it should never be grey.
374         ColorExtractor.GradientColors systemColors = mColorExtractor.getColors(
375                 ColorExtractor.TYPE_DARK, WallpaperManager.FLAG_SYSTEM, true);
376         // We don't want to interpolate colors because we're defining the initial state.
377         // Gradient should be set/ready when you open "Recents".
378         mRecentsView.setScrimColors(systemColors, false);
379 
380         // Notify of the next draw
381         mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
382 
383         // If Recents was restarted, then it should complete the enter animation with partially
384         // reset launch state with dock, app and home set to false
385         Object isRelaunching = getLastNonConfigurationInstance();
386         if (isRelaunching != null && isRelaunching instanceof Boolean && (boolean) isRelaunching) {
387             RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
388             launchState.launchedViaDockGesture = false;
389             launchState.launchedFromApp = false;
390             launchState.launchedFromHome = false;
391             onEnterAnimationComplete();
392         }
393         mRecentsStartRequested = false;
394     }
395 
396     @Override
onColorsChanged(ColorExtractor colorExtractor, int which)397     public void onColorsChanged(ColorExtractor colorExtractor, int which) {
398         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
399             // Recents doesn't care about the wallpaper being visible or not, it always
400             // wants to scrim with wallpaper colors
401             ColorExtractor.GradientColors colors = mColorExtractor.getColors(
402                     WallpaperManager.FLAG_SYSTEM,
403                     ColorExtractor.TYPE_DARK, true /* ignoreVis */);
404             boolean darkText = colors.supportsDarkText();
405             if (darkText != mUsingDarkText) {
406                 mUsingDarkText = darkText;
407                 setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light
408                         : R.style.RecentsTheme_Wallpaper);
409                 mRecentsView.reevaluateStyles();
410             }
411             mRecentsView.setScrimColors(colors, true /* animated */);
412         }
413     }
414 
415     /**
416      * Reloads the stack views upon launching Recents.
417      */
reloadStackView()418     private void reloadStackView() {
419         // If the Recents component has preloaded a load plan, then use that to prevent
420         // reconstructing the task stack
421         RecentsTaskLoader loader = Recents.getTaskLoader();
422         RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
423         if (loadPlan == null) {
424             loadPlan = new RecentsTaskLoadPlan(this);
425         }
426 
427         // Start loading tasks according to the load plan
428         RecentsConfiguration config = Recents.getConfiguration();
429         RecentsActivityLaunchState launchState = config.getLaunchState();
430         if (!loadPlan.hasTasks()) {
431             loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
432         }
433 
434         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
435         loadOpts.runningTaskId = launchState.launchedToTaskId;
436         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
437         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
438         loader.loadTasks(loadPlan, loadOpts);
439         TaskStack stack = loadPlan.getTaskStack();
440         mRecentsView.onReload(stack, mIsVisible);
441 
442         // Update the nav bar scrim, but defer the animation until the enter-window event
443         boolean animateNavBarScrim = !launchState.launchedViaDockGesture;
444         mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null);
445 
446         // If this is a new instance relaunched by AM, without going through the normal mechanisms,
447         // then we have to manually trigger the enter animation state
448         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
449                 !launchState.launchedFromApp;
450         if (wasLaunchedByAm) {
451             EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
452         }
453 
454         // Keep track of whether we launched from the nav bar button or via alt-tab
455         if (launchState.launchedWithAltTab) {
456             MetricsLogger.count(this, "overview_trigger_alttab", 1);
457         } else {
458             MetricsLogger.count(this, "overview_trigger_nav_btn", 1);
459         }
460 
461         // Keep track of whether we launched from an app or from home
462         if (launchState.launchedFromApp) {
463             Task launchTarget = stack.getLaunchTarget();
464             int launchTaskIndexInStack = launchTarget != null
465                     ? stack.indexOfTask(launchTarget)
466                     : 0;
467             MetricsLogger.count(this, "overview_source_app", 1);
468             // If from an app, track the stack index of the app in the stack (for affiliated tasks)
469             MetricsLogger.histogram(this, "overview_source_app_index", launchTaskIndexInStack);
470         } else {
471             MetricsLogger.count(this, "overview_source_home", 1);
472         }
473 
474         // Keep track of the total stack task count
475         int taskCount = mRecentsView.getStack().getTaskCount();
476         MetricsLogger.histogram(this, "overview_task_count", taskCount);
477 
478         // After we have resumed, set the visible state until the next onStop() call
479         mIsVisible = true;
480     }
481 
482     @Override
onEnterAnimationComplete()483     public void onEnterAnimationComplete() {
484         super.onEnterAnimationComplete();
485         EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
486 
487         // Workaround for b/64694148: The animation started callback is not made (see
488         // RecentsImpl.getThumbnailTransitionActivityOptions) so reset the transition-waiting state
489         // once the enter animation has completed.
490         EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
491     }
492 
493     @Override
onRetainNonConfigurationInstance()494     public Object onRetainNonConfigurationInstance() {
495         return true;
496     }
497 
498     @Override
onPause()499     protected void onPause() {
500         super.onPause();
501 
502         mIgnoreAltTabRelease = false;
503     }
504 
505     @Override
onConfigurationChanged(Configuration newConfig)506     public void onConfigurationChanged(Configuration newConfig) {
507         super.onConfigurationChanged(newConfig);
508 
509         // Notify of the config change
510         Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
511         int numStackTasks = mRecentsView.getStack().getTaskCount();
512         EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
513                 mLastConfig.orientation != newDeviceConfiguration.orientation,
514                 mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0));
515 
516         mLastConfig.updateFrom(newDeviceConfiguration);
517     }
518 
519     @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode)520     public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
521         super.onMultiWindowModeChanged(isInMultiWindowMode);
522 
523         // Set the window background
524         mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode);
525 
526         // Reload the task stack view if we are still visible to pick up the change in tasks that
527         // result from entering/exiting multi-window
528         if (mIsVisible) {
529             reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
530         }
531     }
532 
533     @Override
onStop()534     protected void onStop() {
535         super.onStop();
536 
537         // Notify that recents is now hidden
538         mIsVisible = false;
539         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
540         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
541         Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
542 
543         // When recents starts again before onStop, do not reset launch flags so entrance animation
544         // can run
545         if (!isChangingConfigurations() && !mRecentsStartRequested) {
546             // Workaround for b/22542869, if the RecentsActivity is started again, but without going
547             // through SystemUI, we need to reset the config launch flags to ensure that we do not
548             // wait on the system to send a signal that was never queued.
549             RecentsConfiguration config = Recents.getConfiguration();
550             RecentsActivityLaunchState launchState = config.getLaunchState();
551             launchState.reset();
552         }
553 
554         // Force a gc to attempt to clean up bitmap references more quickly (b/38258699)
555         Recents.getSystemServices().gc();
556     }
557 
558     @Override
onDestroy()559     protected void onDestroy() {
560         super.onDestroy();
561 
562         // In the case that the activity finished on startup, just skip the unregistration below
563         if (mFinishedOnStartup) {
564             return;
565         }
566 
567         // Unregister the system broadcast receivers
568         unregisterReceiver(mSystemBroadcastReceiver);
569 
570         // Unregister any broadcast receivers for the task loader
571         mPackageMonitor.unregister();
572 
573         EventBus.getDefault().unregister(this);
574     }
575 
576     @Override
onAttachedToWindow()577     public void onAttachedToWindow() {
578         super.onAttachedToWindow();
579         EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY);
580     }
581 
582     @Override
onDetachedFromWindow()583     public void onDetachedFromWindow() {
584         super.onDetachedFromWindow();
585         EventBus.getDefault().unregister(mScrimViews);
586     }
587 
588     @Override
onTrimMemory(int level)589     public void onTrimMemory(int level) {
590         RecentsTaskLoader loader = Recents.getTaskLoader();
591         if (loader != null) {
592             loader.onTrimMemory(level);
593         }
594     }
595 
596     @Override
onKeyDown(int keyCode, KeyEvent event)597     public boolean onKeyDown(int keyCode, KeyEvent event) {
598         switch (keyCode) {
599             case KeyEvent.KEYCODE_TAB: {
600                 int altTabKeyDelay = getResources().getInteger(R.integer.recents_alt_tab_key_delay);
601                 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
602                         mLastTabKeyEventTime) > altTabKeyDelay;
603                 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
604                     // Focus the next task in the stack
605                     final boolean backward = event.isShiftPressed();
606                     if (backward) {
607                         EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
608                     } else {
609                         EventBus.getDefault().send(new FocusNextTaskViewEvent());
610                     }
611                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
612 
613                     // In the case of another ALT event, don't ignore the next release
614                     if (event.isAltPressed()) {
615                         mIgnoreAltTabRelease = false;
616                     }
617                 }
618                 return true;
619             }
620             case KeyEvent.KEYCODE_DPAD_UP:
621             case KeyEvent.KEYCODE_DPAD_DOWN:
622             case KeyEvent.KEYCODE_DPAD_LEFT:
623             case KeyEvent.KEYCODE_DPAD_RIGHT: {
624                 final Direction direction = NavigateTaskViewEvent.getDirectionFromKeyCode(keyCode);
625                 EventBus.getDefault().send(new NavigateTaskViewEvent(direction));
626                 return true;
627             }
628             case KeyEvent.KEYCODE_DEL:
629             case KeyEvent.KEYCODE_FORWARD_DEL: {
630                 if (event.getRepeatCount() <= 0) {
631                     EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
632 
633                     // Keep track of deletions by keyboard
634                     MetricsLogger.histogram(this, "overview_task_dismissed_source",
635                             Constants.Metrics.DismissSourceKeyboard);
636                     return true;
637                 }
638             }
639             default:
640                 break;
641         }
642         return super.onKeyDown(keyCode, event);
643     }
644 
645     @Override
onUserInteraction()646     public void onUserInteraction() {
647         EventBus.getDefault().send(mUserInteractionEvent);
648     }
649 
650     @Override
onBackPressed()651     public void onBackPressed() {
652         // Back behaves like the recents button so just trigger a toggle event
653         EventBus.getDefault().send(new ToggleRecentsEvent());
654     }
655 
656     /**** EventBus events ****/
657 
onBusEvent(ToggleRecentsEvent event)658     public final void onBusEvent(ToggleRecentsEvent event) {
659         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
660         if (launchState.launchedFromHome) {
661             dismissRecentsToHome(true /* animateTaskViews */);
662         } else {
663             dismissRecentsToLaunchTargetTaskOrHome();
664         }
665     }
666 
onBusEvent(RecentsActivityStartingEvent event)667     public final void onBusEvent(RecentsActivityStartingEvent event) {
668         mRecentsStartRequested = true;
669     }
670 
onBusEvent(HideRecentsEvent event)671     public final void onBusEvent(HideRecentsEvent event) {
672         if (event.triggeredFromAltTab) {
673             // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
674             if (!mIgnoreAltTabRelease) {
675                 dismissRecentsToFocusedTaskOrHome();
676             }
677         } else if (event.triggeredFromHomeKey) {
678             dismissRecentsToHome(true /* animateTaskViews */);
679 
680             // Cancel any pending dozes
681             EventBus.getDefault().send(mUserInteractionEvent);
682         } else {
683             // Do nothing
684         }
685     }
686 
onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event)687     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
688         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
689         mRecentsView.invalidate();
690     }
691 
onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event)692     public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
693         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
694         mRecentsView.invalidate();
695     }
696 
onBusEvent(DockedFirstAnimationFrameEvent event)697     public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
698         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
699         mRecentsView.invalidate();
700     }
701 
onBusEvent(CancelEnterRecentsWindowAnimationEvent event)702     public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
703         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
704         int launchToTaskId = launchState.launchedToTaskId;
705         if (launchToTaskId != -1 &&
706                 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
707             ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
708             am.cancelWindowTransition(launchState.launchedToTaskId);
709         }
710     }
711 
onBusEvent(ShowApplicationInfoEvent event)712     public final void onBusEvent(ShowApplicationInfoEvent event) {
713         // Create a new task stack with the application info details activity
714         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
715                 Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null));
716         intent.setComponent(intent.resolveActivity(getPackageManager()));
717         TaskStackBuilder.create(this)
718                 .addNextIntentWithParentStack(intent).startActivities(null,
719                         new UserHandle(event.task.key.userId));
720 
721         // Keep track of app-info invocations
722         MetricsLogger.count(this, "overview_app_info", 1);
723     }
724 
onBusEvent(ShowIncompatibleAppOverlayEvent event)725     public final void onBusEvent(ShowIncompatibleAppOverlayEvent event) {
726         if (mIncompatibleAppOverlay == null) {
727             mIncompatibleAppOverlay = Utilities.findViewStubById(this,
728                     R.id.incompatible_app_overlay_stub).inflate();
729             mIncompatibleAppOverlay.setWillNotDraw(false);
730             mIncompatibleAppOverlay.setVisibility(View.VISIBLE);
731         }
732         mIncompatibleAppOverlay.animate()
733                 .alpha(1f)
734                 .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION)
735                 .setInterpolator(Interpolators.ALPHA_IN)
736                 .start();
737     }
738 
onBusEvent(HideIncompatibleAppOverlayEvent event)739     public final void onBusEvent(HideIncompatibleAppOverlayEvent event) {
740         if (mIncompatibleAppOverlay != null) {
741             mIncompatibleAppOverlay.animate()
742                     .alpha(0f)
743                     .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION)
744                     .setInterpolator(Interpolators.ALPHA_OUT)
745                     .start();
746         }
747     }
748 
onBusEvent(DeleteTaskDataEvent event)749     public final void onBusEvent(DeleteTaskDataEvent event) {
750         // Remove any stored data from the loader
751         RecentsTaskLoader loader = Recents.getTaskLoader();
752         loader.deleteTaskData(event.task, false);
753 
754         // Remove the task from activity manager
755         ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
756     }
757 
onBusEvent(TaskViewDismissedEvent event)758     public final void onBusEvent(TaskViewDismissedEvent event) {
759         mRecentsView.updateScrimOpacity();
760     }
761 
onBusEvent(AllTaskViewsDismissedEvent event)762     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
763         SystemServicesProxy ssp = Recents.getSystemServices();
764         if (ssp.hasDockedTask()) {
765             mRecentsView.showEmptyView(event.msgResId);
766         } else {
767             // Just go straight home (no animation necessary because there are no more task views)
768             dismissRecentsToHome(false /* animateTaskViews */);
769         }
770 
771         // Keep track of all-deletions
772         MetricsLogger.count(this, "overview_task_all_dismissed", 1);
773     }
774 
onBusEvent(LaunchTaskSucceededEvent event)775     public final void onBusEvent(LaunchTaskSucceededEvent event) {
776         MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
777     }
778 
onBusEvent(LaunchTaskFailedEvent event)779     public final void onBusEvent(LaunchTaskFailedEvent event) {
780         // Return to Home
781         dismissRecentsToHome(true /* animateTaskViews */);
782 
783         MetricsLogger.count(this, "overview_task_launch_failed", 1);
784     }
785 
onBusEvent(ScreenPinningRequestEvent event)786     public final void onBusEvent(ScreenPinningRequestEvent event) {
787         MetricsLogger.count(this, "overview_screen_pinned", 1);
788     }
789 
onBusEvent(StackViewScrolledEvent event)790     public final void onBusEvent(StackViewScrolledEvent event) {
791         // Once the user has scrolled while holding alt-tab, then we should ignore the release of
792         // the key
793         mIgnoreAltTabRelease = true;
794     }
795 
onBusEvent(final DockedTopTaskEvent event)796     public final void onBusEvent(final DockedTopTaskEvent event) {
797         mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
798         mRecentsView.invalidate();
799     }
800 
onBusEvent(final ActivityUnpinnedEvent event)801     public final void onBusEvent(final ActivityUnpinnedEvent event) {
802         if (mIsVisible) {
803             // Skip the configuration change event as the PiP activity does not actually affect the
804             // config of recents
805             reloadTaskStack(isInMultiWindowMode(), false /* sendConfigChangedEvent */);
806         }
807     }
808 
reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent)809     private void reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent) {
810         // Reload the task stack completely
811         RecentsConfiguration config = Recents.getConfiguration();
812         RecentsActivityLaunchState launchState = config.getLaunchState();
813         RecentsTaskLoader loader = Recents.getTaskLoader();
814         RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
815         loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
816 
817         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
818         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
819         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
820         loader.loadTasks(loadPlan, loadOpts);
821 
822         TaskStack stack = loadPlan.getTaskStack();
823         int numStackTasks = stack.getTaskCount();
824         boolean showDeferredAnimation = numStackTasks > 0;
825 
826         if (sendConfigChangedEvent) {
827             EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
828                     false /* fromDeviceOrientationChange */, false /* fromDisplayDensityChange */,
829                     numStackTasks > 0));
830         }
831         EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode,
832                 showDeferredAnimation, stack));
833     }
834 
835     @Override
onPreDraw()836     public boolean onPreDraw() {
837         mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
838         return true;
839     }
840 
onPackageChanged(String packageName, int userId)841     public void onPackageChanged(String packageName, int userId) {
842         Recents.getTaskLoader().onPackageChanged(packageName);
843         EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
844     }
845 
846     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)847     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
848         super.dump(prefix, fd, writer, args);
849         EventBus.getDefault().dump(prefix, writer);
850         Recents.getTaskLoader().dump(prefix, writer);
851 
852         String id = Integer.toHexString(System.identityHashCode(this));
853 
854         writer.print(prefix); writer.print(TAG);
855         writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
856         writer.print(" currentTime="); writer.print(System.currentTimeMillis());
857         writer.print(" [0x"); writer.print(id); writer.print("]");
858         writer.println();
859 
860         if (mRecentsView != null) {
861             mRecentsView.dump(prefix, writer);
862         }
863     }
864 }
865