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