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