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