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