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