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