RecentsActivity.java revision 083baf99ff1228e96ede96aac88c8200c4fdc2b2
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.appwidget.AppWidgetHostView;
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.SharedPreferences;
30import android.os.Bundle;
31import android.os.UserHandle;
32import android.util.Pair;
33import android.view.KeyEvent;
34import android.view.View;
35import android.view.ViewStub;
36import android.widget.Toast;
37import com.android.systemui.R;
38import com.android.systemui.recents.misc.Console;
39import com.android.systemui.recents.misc.DebugTrigger;
40import com.android.systemui.recents.misc.ReferenceCountedTrigger;
41import com.android.systemui.recents.misc.SystemServicesProxy;
42import com.android.systemui.recents.misc.Utilities;
43import com.android.systemui.recents.model.RecentsTaskLoader;
44import com.android.systemui.recents.model.SpaceNode;
45import com.android.systemui.recents.model.TaskStack;
46import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
47import com.android.systemui.recents.views.RecentsView;
48import com.android.systemui.recents.views.SystemBarScrimViews;
49import com.android.systemui.recents.views.ViewAnimation;
50
51import java.lang.ref.WeakReference;
52import java.lang.reflect.InvocationTargetException;
53import java.util.ArrayList;
54
55/* Activity */
56public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
57        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
58        FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
59
60    final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
61    final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
62    final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
63    final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
64
65    RecentsView mRecentsView;
66    SystemBarScrimViews mScrimViews;
67    ViewStub mEmptyViewStub;
68    View mEmptyView;
69    ViewStub mFullscreenOverlayStub;
70    FullscreenTransitionOverlayView mFullScreenOverlayView;
71
72    RecentsConfiguration mConfig;
73
74    RecentsAppWidgetHost mAppWidgetHost;
75    AppWidgetProviderInfo mSearchAppWidgetInfo;
76    AppWidgetHostView mSearchAppWidgetHostView;
77
78    boolean mVisible;
79
80    // Runnables to finish the Recents activity
81    FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
82    FinishRecentsRunnable mFinishLaunchHomeRunnable;
83
84    /**
85     * A Runnable to finish Recents either with/without a transition, and either by calling finish()
86     * or just launching the specified intent.
87     */
88    class FinishRecentsRunnable implements Runnable {
89        boolean mUseCustomFinishTransition;
90        Intent mLaunchIntent;
91        ActivityOptions mLaunchOpts;
92
93        public FinishRecentsRunnable(boolean withTransition) {
94            mUseCustomFinishTransition = withTransition;
95        }
96
97        /**
98         * Creates a finish runnable that starts the specified intent, using the given
99         * ActivityOptions.
100         */
101        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
102            mLaunchIntent = launchIntent;
103            mLaunchOpts = opts;
104        }
105
106        @Override
107        public void run() {
108            // Mark Recents as no longer visible
109            AlternateRecentsComponent.notifyVisibilityChanged(false);
110            mVisible = false;
111            // Finish Recents
112            if (mLaunchIntent != null) {
113                if (mLaunchOpts != null) {
114                    startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT));
115                } else {
116                    startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(),
117                            new UserHandle(UserHandle.USER_CURRENT));
118                }
119            } else {
120                finish();
121                if (mUseCustomFinishTransition) {
122                    overridePendingTransition(R.anim.recents_to_launcher_enter,
123                            R.anim.recents_to_launcher_exit);
124                }
125            }
126        }
127    }
128
129    // Broadcast receiver to handle messages from AlternateRecentsComponent
130    final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
131        @Override
132        public void onReceive(Context context, Intent intent) {
133            String action = intent.getAction();
134            if (Console.Enabled) {
135                Console.log(Constants.Log.App.SystemUIHandshake,
136                        "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
137            }
138            if (action.equals(ACTION_HIDE_RECENTS_ACTIVITY)) {
139                if (intent.getBooleanExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
140                    // Dismiss recents, launching the focused task
141                    dismissRecentsIfVisible();
142                } else {
143                    // If we are mid-animation into Recents, then reverse it and finish
144                    if (mFullScreenOverlayView == null ||
145                            !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
146                        // Otherwise, either finish Recents, or launch Home directly
147                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
148                                null, mFinishLaunchHomeRunnable, null);
149                        mRecentsView.startExitToHomeAnimation(
150                                new ViewAnimation.TaskViewExitContext(exitTrigger));
151                    }
152                }
153            } else if (action.equals(ACTION_TOGGLE_RECENTS_ACTIVITY)) {
154                // Try and unfilter and filtered stacks
155                if (!mRecentsView.unfilterFilteredStacks()) {
156                    // If there are no filtered stacks, dismiss recents and launch the first task
157                    dismissRecentsIfVisible();
158                }
159            } else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
160                // Try and start the enter animation (or restart it on configuration changed)
161                ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
162                mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
163                        mFullScreenOverlayView, t));
164                // Call our callback
165                onEnterAnimationTriggered();
166            }
167        }
168    };
169
170    // Broadcast receiver to handle messages from the system
171    final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
172        @Override
173        public void onReceive(Context context, Intent intent) {
174            String action = intent.getAction();
175            if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) {
176                mFinishLaunchHomeRunnable.run();
177            } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
178                // Refresh the search widget
179                refreshSearchWidget();
180            }
181        }
182    };
183
184    // Debug trigger
185    final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
186        @Override
187        public void run() {
188            onDebugModeTriggered();
189        }
190    });
191
192    /** Updates the set of recent tasks */
193    void updateRecentsTasks(Intent launchIntent) {
194        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
195        SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
196        ArrayList<TaskStack> stacks = root.getStacks();
197        if (!stacks.isEmpty()) {
198            mRecentsView.setBSP(root);
199        }
200
201        // Update the configuration based on the launch intent
202        mConfig.launchedFromHome = launchIntent.getBooleanExtra(
203                AlternateRecentsComponent.EXTRA_FROM_HOME, false);
204        mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
205                AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
206        mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
207                AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
208        mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
209                AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
210        mConfig.launchedWithNoRecentTasks = !root.hasTasks();
211        mConfig.launchedToTaskId = launchIntent.getIntExtra(
212                AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
213
214        // Add the default no-recents layout
215        if (mEmptyView == null) {
216            mEmptyView = mEmptyViewStub.inflate();
217        }
218        if (mConfig.launchedWithNoRecentTasks) {
219            mEmptyView.setVisibility(View.VISIBLE);
220            mRecentsView.setSearchBarVisibility(View.GONE);
221        } else {
222            mEmptyView.setVisibility(View.GONE);
223            if (mRecentsView.hasSearchBar()) {
224                mRecentsView.setSearchBarVisibility(View.VISIBLE);
225            } else {
226                addSearchBarAppWidgetView();
227            }
228        }
229
230        // Show the scrim if we animate into Recents without window transitions
231        mScrimViews.prepareEnterRecentsAnimation();
232    }
233
234    /** Attempts to allocate and bind the search bar app widget */
235    void bindSearchBarAppWidget() {
236        if (Constants.DebugFlags.App.EnableSearchLayout) {
237            SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
238
239            // Reset the host view and widget info
240            mSearchAppWidgetHostView = null;
241            mSearchAppWidgetInfo = null;
242
243            // Try and load the app widget id from the settings
244            int appWidgetId = mConfig.searchBarAppWidgetId;
245            if (appWidgetId >= 0) {
246                mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
247                if (mSearchAppWidgetInfo == null) {
248                    // If there is no actual widget associated with that id, then delete it and
249                    // prepare to bind another app widget in its place
250                    ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
251                    appWidgetId = -1;
252                }
253                if (Console.Enabled) {
254                    Console.log(Constants.Log.App.SystemUIHandshake,
255                            "[RecentsActivity|onCreate|settings|appWidgetId]",
256                            "Id: " + appWidgetId,
257                            Console.AnsiBlue);
258                }
259            }
260
261            // If there is no id, then bind a new search app widget
262            if (appWidgetId < 0) {
263                Pair<Integer, AppWidgetProviderInfo> widgetInfo =
264                        ssp.bindSearchAppWidget(mAppWidgetHost);
265                if (widgetInfo != null) {
266                    if (Console.Enabled) {
267                        Console.log(Constants.Log.App.SystemUIHandshake,
268                                "[RecentsActivity|onCreate|searchWidget]",
269                                "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
270                                Console.AnsiBlue);
271                    }
272
273                    // Save the app widget id into the settings
274                    mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
275                    mSearchAppWidgetInfo = widgetInfo.second;
276                }
277            }
278        }
279    }
280
281    /** Creates the search bar app widget view */
282    void addSearchBarAppWidgetView() {
283        if (Constants.DebugFlags.App.EnableSearchLayout) {
284            int appWidgetId = mConfig.searchBarAppWidgetId;
285            if (appWidgetId >= 0) {
286                if (Console.Enabled) {
287                    Console.log(Constants.Log.App.SystemUIHandshake,
288                            "[RecentsActivity|onCreate|addSearchAppWidgetView]",
289                            "Id: " + appWidgetId,
290                            Console.AnsiBlue);
291                }
292                mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
293                        mSearchAppWidgetInfo);
294                Bundle opts = new Bundle();
295                opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
296                        AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
297                mSearchAppWidgetHostView.updateAppWidgetOptions(opts);
298                // Set the padding to 0 for this search widget
299                mSearchAppWidgetHostView.setPadding(0, 0, 0, 0);
300                mRecentsView.setSearchBar(mSearchAppWidgetHostView);
301            } else {
302                mRecentsView.setSearchBar(null);
303            }
304        }
305    }
306
307    /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
308    boolean dismissRecentsIfVisible() {
309        if (mVisible) {
310            // If we are mid-animation into Recents, then reverse it and finish
311            if (mFullScreenOverlayView == null ||
312                    !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
313                // If we have a focused task, then launch that task
314                if (!mRecentsView.launchFocusedTask()) {
315                    if (mConfig.launchedFromHome) {
316                        // Just start the animation out of recents
317                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
318                                null, mFinishLaunchHomeRunnable, null);
319                        mRecentsView.startExitToHomeAnimation(
320                                new ViewAnimation.TaskViewExitContext(exitTrigger));
321                    } else {
322                        // Otherwise, try and launch the first task
323                        if (!mRecentsView.launchFirstTask()) {
324                            // If there are no tasks, then just finish recents
325                            mFinishLaunchHomeRunnable.run();
326                        }
327                    }
328                }
329            }
330            return true;
331        }
332        return false;
333    }
334
335    /** Called with the activity is first created. */
336    @Override
337    public void onCreate(Bundle savedInstanceState) {
338        super.onCreate(savedInstanceState);
339        if (Console.Enabled) {
340            Console.logDivider(Constants.Log.App.SystemUIHandshake);
341            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
342                    getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
343            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
344                    Constants.Log.App.TimeRecentsStartupKey, "onCreate");
345        }
346
347        // Initialize the loader and the configuration
348        RecentsTaskLoader.initialize(this);
349        mConfig = RecentsConfiguration.reinitialize(this);
350
351        // Create the home intent runnable
352        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
353        homeIntent.addCategory(Intent.CATEGORY_HOME);
354        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
355                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
356        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
357                ActivityOptions.makeCustomAnimation(this, R.anim.recents_to_launcher_enter,
358                        R.anim.recents_to_launcher_exit));
359
360        // Initialize the widget host (the host id is static and does not change)
361        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
362
363        // Set the Recents layout
364        setContentView(R.layout.recents);
365        mRecentsView = (RecentsView) findViewById(R.id.recents_view);
366        mRecentsView.setCallbacks(this);
367        mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
368                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
369                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
370        mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
371        mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
372        mScrimViews = new SystemBarScrimViews(this, mConfig);
373
374        // Bind the search app widget when we first start up
375        bindSearchBarAppWidget();
376        // Update the recent tasks
377        updateRecentsTasks(getIntent());
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        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
383        registerReceiver(mSystemBroadcastReceiver, filter);
384
385        // Register any broadcast receivers for the task loader
386        RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
387
388        // Private API calls to make the shadows look better
389        try {
390            Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f));
391            Utilities.setShadowProperty("ambientRatio", String.valueOf(0.5f));
392        } catch (IllegalAccessException e) {
393            e.printStackTrace();
394        } catch (InvocationTargetException e) {
395            e.printStackTrace();
396        }
397
398        // Prepare the screenshot transition if necessary
399        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
400            mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
401            mFullScreenOverlayView.setCallbacks(this);
402            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
403        }
404
405        // Update if we are getting a configuration change
406        if (savedInstanceState != null) {
407            mConfig.updateOnConfigurationChange();
408            onConfigurationChange();
409        }
410    }
411
412    void onConfigurationChange() {
413        // Try and start the enter animation (or restart it on configuration changed)
414        ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
415        mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
416                mFullScreenOverlayView, t));
417        // Call our callback
418        onEnterAnimationTriggered();
419    }
420
421    @Override
422    protected void onNewIntent(Intent intent) {
423        super.onNewIntent(intent);
424        setIntent(intent);
425
426        if (Console.Enabled) {
427            Console.logDivider(Constants.Log.App.SystemUIHandshake);
428            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
429                    intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
430            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
431                    Constants.Log.App.TimeRecentsStartupKey, "onNewIntent");
432        }
433
434        // Initialize the loader and the configuration
435        RecentsTaskLoader.initialize(this);
436        mConfig = RecentsConfiguration.reinitialize(this);
437
438        // Update the recent tasks
439        updateRecentsTasks(intent);
440
441        // Prepare the screenshot transition if necessary
442        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
443            mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
444        }
445    }
446
447    @Override
448    protected void onStart() {
449        if (Console.Enabled) {
450            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
451                    Console.AnsiRed);
452        }
453        super.onStart();
454
455        // Register the broadcast receiver to handle messages from our service
456        IntentFilter filter = new IntentFilter();
457        filter.addAction(ACTION_HIDE_RECENTS_ACTIVITY);
458        filter.addAction(ACTION_TOGGLE_RECENTS_ACTIVITY);
459        filter.addAction(ACTION_START_ENTER_ANIMATION);
460        registerReceiver(mServiceBroadcastReceiver, filter);
461
462        mVisible = true;
463    }
464
465    @Override
466    protected void onResume() {
467        if (Console.Enabled) {
468            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
469                    Console.AnsiRed);
470        }
471        super.onResume();
472
473        // Start listening for widget package changes if there is one bound, post it since we don't
474        // want it stalling the startup
475        if (mConfig.searchBarAppWidgetId >= 0) {
476            final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> callback =
477                    new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(this);
478            mRecentsView.postDelayed(new Runnable() {
479                @Override
480                public void run() {
481                    RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = callback.get();
482                    if (cb != null) {
483                        mAppWidgetHost.startListening(cb);
484                    }
485                }
486            }, 1);
487        }
488    }
489
490    @Override
491    public void onAttachedToWindow() {
492        if (Console.Enabled) {
493            Console.log(Constants.Log.App.SystemUIHandshake,
494                    "[RecentsActivity|onAttachedToWindow]", "",
495                    Console.AnsiRed);
496        }
497        super.onAttachedToWindow();
498    }
499
500    @Override
501    public void onDetachedFromWindow() {
502        if (Console.Enabled) {
503            Console.log(Constants.Log.App.SystemUIHandshake,
504                    "[RecentsActivity|onDetachedFromWindow]", "",
505                    Console.AnsiRed);
506        }
507        super.onDetachedFromWindow();
508    }
509
510    @Override
511    protected void onPause() {
512        if (Console.Enabled) {
513            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
514                    Console.AnsiRed);
515        }
516        super.onPause();
517    }
518
519    @Override
520    protected void onStop() {
521        if (Console.Enabled) {
522            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
523                    Console.AnsiRed);
524        }
525        super.onStop();
526
527        // Unregister the RecentsService receiver
528        unregisterReceiver(mServiceBroadcastReceiver);
529
530        // Stop listening for widget package changes if there was one bound
531        if (mConfig.searchBarAppWidgetId >= 0) {
532            mAppWidgetHost.stopListening();
533        }
534    }
535
536    @Override
537    protected void onDestroy() {
538        if (Console.Enabled) {
539            Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "",
540                    Console.AnsiRed);
541        }
542        super.onDestroy();
543
544        // Unregister the screen off receiver
545        unregisterReceiver(mSystemBroadcastReceiver);
546        RecentsTaskLoader.getInstance().unregisterReceivers();
547    }
548
549    @Override
550    public void onTrimMemory(int level) {
551        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
552        if (loader != null) {
553            loader.onTrimMemory(level);
554        }
555    }
556
557    @Override
558    public boolean onKeyDown(int keyCode, KeyEvent event) {
559        if (keyCode == KeyEvent.KEYCODE_TAB) {
560            // Focus the next task in the stack
561            final boolean backward = event.isShiftPressed();
562            mRecentsView.focusNextTask(!backward);
563            return true;
564        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
565            mRecentsView.focusNextTask(true);
566            return true;
567        } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
568            mRecentsView.focusNextTask(false);
569            return true;
570        }
571        // Pass through the debug trigger
572        mDebugTrigger.onKeyEvent(keyCode);
573        return super.onKeyDown(keyCode, event);
574    }
575
576    @Override
577    public void onUserInteraction() {
578        mRecentsView.onUserInteraction();
579    }
580
581    @Override
582    public void onBackPressed() {
583        // Test mode where back does not do anything
584        if (mConfig.debugModeEnabled) return;
585
586        // If we are mid-animation into Recents, then reverse it and finish
587        if (mFullScreenOverlayView == null ||
588                !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
589            // If we are currently filtering in any stacks, unfilter them first
590            if (!mRecentsView.unfilterFilteredStacks()) {
591                if (mConfig.launchedFromHome) {
592                    // Just start the animation out of recents
593                    ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
594                            null, mFinishLaunchHomeRunnable, null);
595                    mRecentsView.startExitToHomeAnimation(
596                            new ViewAnimation.TaskViewExitContext(exitTrigger));
597                } else {
598                    // Otherwise, try and launch the first task
599                    if (!mRecentsView.launchFirstTask()) {
600                        // If there are no tasks, then just finish recents
601                        mFinishLaunchHomeRunnable.run();
602                    }
603                }
604            }
605        }
606    }
607
608    /** Called when debug mode is triggered */
609    public void onDebugModeTriggered() {
610        if (mConfig.developerOptionsEnabled) {
611            SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
612            if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
613                // Disable the debug mode
614                settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
615            } else {
616                // Enable the debug mode
617                settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
618            }
619            Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
620                    ") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
621        }
622    }
623
624    /** Called when the enter recents animation is triggered. */
625    public void onEnterAnimationTriggered() {
626        // Animate the scrims in
627        mScrimViews.startEnterRecentsAnimation();
628    }
629
630    /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
631
632    @Override
633    public void onEnterAnimationComplete() {
634        // Reset the full screenshot transition view
635        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
636            mFullScreenOverlayView.reset();
637
638            // Recycle the full screen screenshot
639            AlternateRecentsComponent.consumeLastScreenshot();
640        }
641    }
642
643    /**** RecentsView.RecentsViewCallbacks Implementation ****/
644
645    @Override
646    public void onExitToHomeAnimationTriggered() {
647        // Animate the scrims out
648        mScrimViews.startExitRecentsAnimation();
649    }
650
651    @Override
652    public void onTaskViewClicked() {
653        // Mark recents as no longer visible
654        AlternateRecentsComponent.notifyVisibilityChanged(false);
655        mVisible = false;
656    }
657
658    @Override
659    public void onAllTaskViewsDismissed() {
660        mFinishLaunchHomeRunnable.run();
661    }
662
663    /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
664
665    @Override
666    public void refreshSearchWidget() {
667        // Load the Search widget again
668        bindSearchBarAppWidget();
669        addSearchBarAppWidgetView();
670    }
671}
672