Home | History | Annotate | Download | only in recents
      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          */
    141         public LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts) {
    142             mLaunchIntent = launchIntent;
    143             mOpts = opts;
    144         }
    145 
    146         @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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. */
    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
    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
    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
    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      */
    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
    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
    440     protected void onPause() {
    441         super.onPause();
    442 
    443         mIgnoreAltTabRelease = false;
    444         mIterateTrigger.stopDozing();
    445     }
    446 
    447     @Override
    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
    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
    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
    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
    526     public void onAttachedToWindow() {
    527         super.onAttachedToWindow();
    528         EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY);
    529     }
    530 
    531     @Override
    532     public void onDetachedFromWindow() {
    533         super.onDetachedFromWindow();
    534         EventBus.getDefault().unregister(mScrimViews);
    535     }
    536 
    537     @Override
    538     public void onTrimMemory(int level) {
    539         RecentsTaskLoader loader = Recents.getTaskLoader();
    540         if (loader != null) {
    541             loader.onTrimMemory(level);
    542         }
    543     }
    544 
    545     @Override
    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
    597     public void onUserInteraction() {
    598         EventBus.getDefault().send(mUserInteractionEvent);
    599     }
    600 
    601     @Override
    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 
    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 
    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 
    641     public final void onBusEvent(UserInteractionEvent event) {
    642         // Stop the fast-toggle dozer
    643         mIterateTrigger.stopDozing();
    644     }
    645 
    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 
    662     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
    663         EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
    664         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
    665         mRecentsView.invalidate();
    666     }
    667 
    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 
    676     public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
    677         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
    678         mRecentsView.invalidate();
    679     }
    680 
    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 
    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 
    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 
    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 
    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 
    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 
    752     public final void onBusEvent(LaunchTaskSucceededEvent event) {
    753         MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
    754     }
    755 
    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 
    763     public final void onBusEvent(ScreenPinningRequestEvent event) {
    764         MetricsLogger.count(this, "overview_screen_pinned", 1);
    765     }
    766 
    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 
    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 
    778     public final void onBusEvent(final DockedTopTaskEvent event) {
    779         mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
    780         mRecentsView.invalidate();
    781     }
    782 
    783     @Override
    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
    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 
    805         writer.print(prefix); writer.print(TAG);
    806         writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
    807         writer.print(" [0x"); writer.print(id); writer.print("]");
    808         writer.println();
    809 
    810         if (mRecentsView != null) {
    811             mRecentsView.dump(prefix, writer);
    812         }
    813     }
    814 }
    815