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