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