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