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