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 static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
20
21import android.app.Activity;
22import android.app.ActivityOptions;
23import android.app.TaskStackBuilder;
24import android.app.WallpaperManager;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.Configuration;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.SystemClock;
35import android.os.UserHandle;
36import android.provider.Settings;
37import android.util.Log;
38import android.view.KeyEvent;
39import android.view.View;
40import android.view.ViewTreeObserver;
41import android.view.ViewTreeObserver.OnPreDrawListener;
42import android.view.WindowManager;
43import android.view.WindowManager.LayoutParams;
44
45import com.android.internal.colorextraction.ColorExtractor;
46import com.android.internal.content.PackageMonitor;
47import com.android.internal.logging.MetricsLogger;
48import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
49import com.android.internal.util.LatencyTracker;
50import com.android.systemui.DejankUtils;
51import com.android.systemui.Dependency;
52import com.android.systemui.Interpolators;
53import com.android.systemui.R;
54import com.android.systemui.colorextraction.SysuiColorExtractor;
55import com.android.systemui.recents.events.EventBus;
56import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
57import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
58import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
59import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
60import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
61import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
62import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
63import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
64import com.android.systemui.recents.events.activity.HideRecentsEvent;
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.PackagesChangedEvent;
69import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
70import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
71import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
72import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
73import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
74import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
75import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
76import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
77import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
78import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
79import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
80import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
81import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
82import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
83import com.android.systemui.recents.events.ui.UserInteractionEvent;
84import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
85import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
86import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
87import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
88import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
89import com.android.systemui.recents.misc.SystemServicesProxy;
90import com.android.systemui.shared.recents.utilities.Utilities;
91import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
92import com.android.systemui.shared.recents.model.RecentsTaskLoader;
93import com.android.systemui.shared.recents.model.Task;
94import com.android.systemui.shared.recents.model.TaskStack;
95import com.android.systemui.recents.views.RecentsView;
96import com.android.systemui.recents.views.SystemBarScrimViews;
97import com.android.systemui.shared.system.ActivityManagerWrapper;
98
99import com.android.systemui.shared.system.WindowManagerWrapper;
100import java.io.FileDescriptor;
101import java.io.PrintWriter;
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 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