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