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