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