Merge "Add security status icons."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b14856c..a949715 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3324,17 +3324,6 @@
                        android:value="com.android.settings.applications.ProcessStatsSummary" />
         </activity-alias>
 
-        <activity-alias android:name="BluetoothDashboardAlias"
-                        android:targetActivity="Settings$BluetoothSettingsActivity">
-            <intent-filter android:priority="7">
-                <action android:name="com.android.settings.action.SETTINGS"/>
-            </intent-filter>
-            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
-                       android:value="com.android.settings.bluetooth.BluetoothSettings" />
-            <meta-data android:name="com.android.settings.category"
-                       android:value="com.android.settings.category.ia.device" />
-        </activity-alias>
-
         <activity-alias android:name="CastDashboardAlias"
                         android:targetActivity="Settings$WifiDisplaySettingsActivity">
             <intent-filter android:priority="6">
diff --git a/res/layout/preference_widget_master_switch.xml b/res/layout/preference_widget_master_switch.xml
new file mode 100644
index 0000000..51d938b
--- /dev/null
+++ b/res/layout/preference_widget_master_switch.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="wrap_content"
+  android:layout_height="match_parent">
+
+    <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:gravity="start|center_vertical"
+      android:orientation="horizontal"
+      android:paddingStart="20dp"
+      android:paddingEnd="20dp"
+      android:paddingTop="16dp"
+      android:paddingBottom="16dp">
+        <View
+          android:layout_width="1dip"
+          android:layout_height="match_parent"
+          android:background="?android:attr/colorSecondary"/>
+    </LinearLayout>
+
+    <Switch android:id="@+id/switchWidget"
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/res/layout/search_saved_query_item.xml b/res/layout/search_saved_query_item.xml
new file mode 100644
index 0000000..71c8482
--- /dev/null
+++ b/res/layout/search_saved_query_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/preference_no_icon_padding_start"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <TextView
+        android:id="@android:id/title"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_weight="1"/>
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/search_suggestion_item_image_size"
+        android:layout_height="@dimen/search_suggestion_item_image_size"
+        android:layout_marginStart="@dimen/search_suggestion_item_image_margin_start"
+        android:layout_marginEnd="@dimen/search_suggestion_item_image_margin_end"
+        android:scaleType="centerInside"
+        android:src="@drawable/ic_search_history"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index da83867..b4e6339 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1583,11 +1583,11 @@
     <string name="wifi_in_airplane_mode">In Airplane mode</string>
     <!-- Checkbox title for option to notify user when open networks are nearby -->
     <string name="wifi_notify_open_networks">Open network notification</string>
-    <!-- Checkbox summary for option to notify user when open networks are nearby [CHAR LIMIT=60]-->
+    <!-- Checkbox summary for option to notify user when open networks are nearby-->
     <string name="wifi_notify_open_networks_summary">Notify whenever a high quality open network is available</string>
     <!-- Checkbox title for option to enable Wi-Fi when saved networks are nearby -->
     <string name="wifi_wakeup">Turn Wi\u2011Fi back on</string>
-    <!-- Checkbox summary for option to enable Wi-Fi when saved networks are nearby [CHAR LIMIT=60]-->
+    <!-- Checkbox summary for option to enable Wi-Fi when saved networks are nearby-->
     <string name="wifi_wakeup_summary">Automatically turn on Wi\u2011Fi near saved networks</string>
     <!-- Checkbox title for option to toggle poor network detection -->
     <string name="wifi_poor_network_detection">Avoid poor connections</string>
diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml
index abf493f..d70cc33 100644
--- a/res/xml/connected_devices.xml
+++ b/res/xml/connected_devices.xml
@@ -18,6 +18,13 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:title="@string/connected_devices_dashboard_title">
 
+    <com.android.settings.widget.MasterSwitchPreference
+      android:fragment="com.android.settings.bluetooth.BluetoothSettings"
+      android:key="toggle_bluetooth"
+      android:title="@string/bluetooth_settings_title"
+      android:icon="@drawable/ic_settings_bluetooth"
+      android:order="-7"/>
+
     <SwitchPreference
         android:key="toggle_nfc"
         android:title="@string/nfc_quick_toggle_title"
diff --git a/res/xml/double_twist_gesture_settings.xml b/res/xml/double_twist_gesture_settings.xml
index e8d0abe..f77d778 100644
--- a/res/xml/double_twist_gesture_settings.xml
+++ b/res/xml/double_twist_gesture_settings.xml
@@ -16,7 +16,8 @@
   -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto">
+                  xmlns:app="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/double_twist_for_camera_mode_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_double_twist_video"
diff --git a/res/xml/input_and_gesture.xml b/res/xml/input_and_gesture.xml
index c30178b..9abc797 100644
--- a/res/xml/input_and_gesture.xml
+++ b/res/xml/input_and_gesture.xml
@@ -15,7 +15,9 @@
   limitations under the License.
   -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/input_gesture_settings_title">
 
     <PreferenceCategory
         android:title="@string/keyboard_and_input_methods_category">
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
index 5aa66bb..cabc805 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
@@ -90,9 +90,6 @@
         final UserManager userManager = UserManager.get(getActivity());
         mEffectiveUserId = userManager.getCredentialOwnerProfile(mUserId);
         mLockPatternUtils = new LockPatternUtils(getActivity());
-        mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
-        mAllowFpAuthentication = mAllowFpAuthentication && !isFingerprintDisabledByAdmin()
-                && !mReturnCredentials && !mIsStrongAuthRequired;
     }
 
     @Override
@@ -141,6 +138,10 @@
     @Override
     public void onResume() {
         super.onResume();
+        mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
+        mAllowFpAuthentication = getActivity().getIntent().getBooleanExtra(
+                        ALLOW_FP_AUTHENTICATION, false)
+                && !isFingerprintDisabledByAdmin() && !mReturnCredentials && !mIsStrongAuthRequired;
         refreshLockScreen();
     }
 
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
index e3791e9..aaf9f3c 100644
--- a/src/com/android/settings/bluetooth/BluetoothEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -24,14 +24,13 @@
 import android.os.Handler;
 import android.os.Message;
 import android.provider.Settings;
-import android.widget.Switch;
 import android.widget.Toast;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.search.Index;
-import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.SwitchWidgetController;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -41,9 +40,8 @@
  * preference. It turns on/off Bluetooth and ensures the summary of the
  * preference reflects the current state.
  */
-public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener {
-    private final Switch mSwitch;
-    private final SwitchBar mSwitchBar;
+public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener {
+    private final SwitchWidgetController mSwitchWidget;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private Context mContext;
     private boolean mValidListener;
@@ -76,19 +74,18 @@
         }
     };
 
-    public BluetoothEnabler(Context context, SwitchBar switchBar,
-            MetricsFeatureProvider metricsFeatureProvider) {
+    public BluetoothEnabler(Context context, SwitchWidgetController switchWidget,
+            MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager) {
         mContext = context;
         mMetricsFeatureProvider = metricsFeatureProvider;
-        mSwitchBar = switchBar;
-        mSwitch = switchBar.getSwitch();
+        mSwitchWidget = switchWidget;
+        mSwitchWidget.setListener(this);
         mValidListener = false;
 
-        LocalBluetoothManager manager = Utils.getLocalBtManager(context);
         if (manager == null) {
             // Bluetooth is not supported
             mLocalAdapter = null;
-            mSwitch.setEnabled(false);
+            mSwitchWidget.setEnabled(false);
         } else {
             mLocalAdapter = manager.getBluetoothAdapter();
         }
@@ -96,16 +93,16 @@
     }
 
     public void setupSwitchBar() {
-        mSwitchBar.show();
+        mSwitchWidget.setupView();
     }
 
     public void teardownSwitchBar() {
-        mSwitchBar.hide();
+        mSwitchWidget.teardownView();
     }
 
     public void resume(Context context) {
         if (mLocalAdapter == null) {
-            mSwitch.setEnabled(false);
+            mSwitchWidget.setEnabled(false);
             return;
         }
 
@@ -116,7 +113,7 @@
         // Bluetooth state is not sticky, so set it manually
         handleStateChanged(mLocalAdapter.getBluetoothState());
 
-        mSwitchBar.addOnSwitchChangeListener(this);
+        mSwitchWidget.startListening();
         mContext.registerReceiver(mReceiver, mIntentFilter);
         mValidListener = true;
     }
@@ -125,47 +122,48 @@
         if (mLocalAdapter == null) {
             return;
         }
-
-        mSwitchBar.removeOnSwitchChangeListener(this);
-        mContext.unregisterReceiver(mReceiver);
-        mValidListener = false;
+        if (mValidListener) {
+            mSwitchWidget.stopListening();
+            mContext.unregisterReceiver(mReceiver);
+            mValidListener = false;
+        }
     }
 
     void handleStateChanged(int state) {
         switch (state) {
             case BluetoothAdapter.STATE_TURNING_ON:
-                mSwitch.setEnabled(false);
+                mSwitchWidget.setEnabled(false);
                 break;
             case BluetoothAdapter.STATE_ON:
                 setChecked(true);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(true);
                 break;
             case BluetoothAdapter.STATE_TURNING_OFF:
-                mSwitch.setEnabled(false);
+                mSwitchWidget.setEnabled(false);
                 break;
             case BluetoothAdapter.STATE_OFF:
                 setChecked(false);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(false);
                 break;
             default:
                 setChecked(false);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(false);
         }
     }
 
     private void setChecked(boolean isChecked) {
-        if (isChecked != mSwitch.isChecked()) {
+        if (isChecked != mSwitchWidget.isChecked()) {
             // set listener to null, so onCheckedChanged won't be called
             // if the checked status on Switch isn't changed by user click
             if (mValidListener) {
-                mSwitchBar.removeOnSwitchChangeListener(this);
+                mSwitchWidget.stopListening();
             }
-            mSwitch.setChecked(isChecked);
+            mSwitchWidget.setChecked(isChecked);
             if (mValidListener) {
-                mSwitchBar.addOnSwitchChangeListener(this);
+                mSwitchWidget.startListening();
             }
         }
     }
@@ -180,13 +178,14 @@
     }
 
     @Override
-    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+    public boolean onSwitchToggled(boolean isChecked) {
         // Show toast message if Bluetooth is not allowed in airplane mode
         if (isChecked &&
                 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
             Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
             // Reset switch to off
-            switchView.setChecked(false);
+            mSwitchWidget.setChecked(false);
+            return false;
         }
 
         mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_BLUETOOTH_TOGGLE, isChecked);
@@ -197,12 +196,13 @@
             // a) The switch should be OFF but it should still be togglable (enabled = True)
             // b) The switch bar should have OFF text.
             if (isChecked && !status) {
-                switchView.setChecked(false);
-                mSwitch.setEnabled(true);
-                mSwitchBar.setTextViewLabel(false);
-                return;
+                mSwitchWidget.setChecked(false);
+                mSwitchWidget.setEnabled(true);
+                mSwitchWidget.updateTitle(false);
+                return false;
             }
         }
-        mSwitch.setEnabled(false);
+        mSwitchWidget.setEnabled(false);
+        return true;
     }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
new file mode 100644
index 0000000..955454f
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.bluetooth;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+import com.android.settings.core.lifecycle.events.OnStart;
+import com.android.settings.core.lifecycle.events.OnStop;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.MasterSwitchPreference;
+import com.android.settings.widget.MasterSwitchController;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public class BluetoothMasterSwitchPreferenceController extends PreferenceController
+        implements BluetoothSummaryHelper.OnSummaryChangeListener,
+        LifecycleObserver, OnResume, OnPause, OnStart, OnStop {
+
+    private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth";
+
+    private LocalBluetoothManager mBluetoothManager;
+    private MasterSwitchPreference mBtPreference;
+    private BluetoothEnabler mBluetoothEnabler;
+    private BluetoothSummaryHelper mSummaryHelper;
+
+    public BluetoothMasterSwitchPreferenceController(Context context,
+            LocalBluetoothManager bluetoothManager) {
+        super(context);
+        mBluetoothManager = bluetoothManager;
+        mSummaryHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+        mSummaryHelper.setOnSummaryChangeListener(this);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mBtPreference = (MasterSwitchPreference) screen.findPreference(KEY_TOGGLE_BLUETOOTH);
+        mBluetoothEnabler = new BluetoothEnabler(mContext,
+            new MasterSwitchController(mBtPreference),
+            FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(), mBluetoothManager);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_TOGGLE_BLUETOOTH;
+    }
+
+    public void onResume() {
+        mSummaryHelper.setListening(true);
+    }
+
+    @Override
+    public void onPause() {
+        mSummaryHelper.setListening(false);
+    }
+
+    @Override
+    public void onStart() {
+        if (mBluetoothEnabler != null) {
+            mBluetoothEnabler.resume(mContext);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mBluetoothEnabler != null) {
+            mBluetoothEnabler.pause();
+        }
+    }
+
+    @Override
+    public void onSummaryChanged(String summary) {
+        if (mBtPreference != null) {
+            mBtPreference.setSummary(summary);
+        }
+    }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index d709726..ba53ea3 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -45,6 +45,7 @@
 import com.android.settings.LinkifyUtils;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.bluetooth.BluetoothSummaryHelper.OnSummaryChangeListener;
 import com.android.settings.dashboard.SummaryLoader;
 import com.android.settings.location.ScanningSettings;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -52,13 +53,12 @@
 import com.android.settings.search.SearchIndexableRaw;
 import com.android.settings.widget.FooterPreference;
 import com.android.settings.widget.SwitchBar;
-import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settings.widget.SwitchBarController;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -148,7 +148,8 @@
         final SettingsActivity activity = (SettingsActivity) getActivity();
         mSwitchBar = activity.getSwitchBar();
 
-        mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar, mMetricsFeatureProvider);
+        mBluetoothEnabler = new BluetoothEnabler(activity, new SwitchBarController(mSwitchBar),
+            mMetricsFeatureProvider, Utils.getLocalBtManager(activity));
         mBluetoothEnabler.setupSwitchBar();
     }
 
@@ -508,113 +509,35 @@
     }
 
     @VisibleForTesting
-    static class SummaryProvider
-            implements SummaryLoader.SummaryProvider, BluetoothCallback {
+    static class SummaryProvider implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
 
         private final LocalBluetoothManager mBluetoothManager;
         private final Context mContext;
         private final SummaryLoader mSummaryLoader;
 
-        private boolean mEnabled;
-        private int mConnectionState;
+        @VisibleForTesting
+        BluetoothSummaryHelper mSummaryHelper;
 
         public SummaryProvider(Context context, SummaryLoader summaryLoader,
                 LocalBluetoothManager bluetoothManager) {
             mBluetoothManager = bluetoothManager;
             mContext = context;
             mSummaryLoader = summaryLoader;
+            mSummaryHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+            mSummaryHelper.setOnSummaryChangeListener(this);
         }
 
         @Override
         public void setListening(boolean listening) {
-            BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (defaultAdapter == null) return;
-            if (listening) {
-                mEnabled = defaultAdapter.isEnabled();
-                mConnectionState = defaultAdapter.getConnectionState();
-                mSummaryLoader.setSummary(this, getSummary());
-                mBluetoothManager.getEventManager().registerCallback(this);
-            } else {
-                mBluetoothManager.getEventManager().unregisterCallback(this);
+            mSummaryHelper.setListening(listening);
+        }
+
+        @Override
+        public void onSummaryChanged(String summary) {
+            if (mSummaryLoader != null) {
+                mSummaryLoader.setSummary(this, summary);
             }
         }
-
-        private CharSequence getSummary() {
-            if (!mEnabled) {
-                return mContext.getString(R.string.bluetooth_disabled);
-            } else if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
-                return mContext.getString(R.string.bluetooth_connected);
-            } else {
-                return mContext.getString(R.string.bluetooth_disconnected);
-            }
-        }
-
-        @Override
-        public void onBluetoothStateChanged(int bluetoothState) {
-            mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
-                    || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
-            mSummaryLoader.setSummary(this, getSummary());
-        }
-
-        @Override
-        public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-            mConnectionState = state;
-            updateConnected();
-            mSummaryLoader.setSummary(this, getSummary());
-        }
-
-        @Override
-        public void onScanningStateChanged(boolean started) {
-
-        }
-
-        @Override
-        public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-
-        }
-
-        @Override
-        public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-
-        }
-
-        @Override
-        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-
-        }
-
-        private void updateConnected() {
-            // Make sure our connection state is up to date.
-            int state = mBluetoothManager.getBluetoothAdapter().getConnectionState();
-            if (state != mConnectionState) {
-                mConnectionState = state;
-                return;
-            }
-            final Collection<CachedBluetoothDevice> devices = getDevices();
-            if (devices == null) {
-                mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-                return;
-            }
-            if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
-                CachedBluetoothDevice connectedDevice = null;
-                for (CachedBluetoothDevice device : devices) {
-                    if (device.isConnected()) {
-                        connectedDevice = device;
-                    }
-                }
-                if (connectedDevice == null) {
-                    // If somehow we think we are connected, but have no connected devices, we
-                    // aren't connected.
-                    mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-                }
-            }
-        }
-
-        private Collection<CachedBluetoothDevice> getDevices() {
-            return mBluetoothManager != null
-                    ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
-                    : null;
-        }
     }
 
     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
diff --git a/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java b/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java
new file mode 100644
index 0000000..2bd6f70
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.text.TextUtils;
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import java.util.Collection;
+
+/**
+ * Helper class that listeners to bluetooth callback and notify client when there is update in
+ * bluetooth summary info.
+ */
+public final class BluetoothSummaryHelper implements BluetoothCallback {
+
+    private OnSummaryChangeListener mListener;
+
+    private final LocalBluetoothManager mBluetoothManager;
+    private final LocalBluetoothAdapter mBluetoothAdapter;
+    private final Context mContext;
+
+    private boolean mEnabled;
+    private int mConnectionState;
+    private String mSummary;
+
+    public interface OnSummaryChangeListener {
+        /**
+         * Called when bluetooth summary has changed.
+         *
+         * @param summary The new bluetooth summary .
+         */
+        void onSummaryChanged(String summary);
+    }
+
+    public BluetoothSummaryHelper(Context context, LocalBluetoothManager bluetoothManager) {
+        mContext = context;
+        mBluetoothManager = bluetoothManager;
+        mBluetoothAdapter = mBluetoothManager != null
+            ? mBluetoothManager.getBluetoothAdapter() : null;
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
+            || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
+        notifyChangeIfNeeded();
+    }
+
+    @Override
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        mConnectionState = state;
+        updateConnected();
+        notifyChangeIfNeeded();
+    }
+
+    @Override
+    public void onScanningStateChanged(boolean started) {
+    }
+
+    @Override
+    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+    }
+
+    public void setOnSummaryChangeListener(OnSummaryChangeListener listener) {
+        mListener = listener;
+    }
+
+    public void setListening(boolean listening) {
+        if (mBluetoothAdapter == null) {
+            return;
+        }
+        if (listening) {
+            mEnabled = mBluetoothAdapter.isEnabled();
+            mConnectionState = mBluetoothAdapter.getConnectionState();
+            notifyChangeIfNeeded();
+            mBluetoothManager.getEventManager().registerCallback(this);
+        } else {
+            mBluetoothManager.getEventManager().unregisterCallback(this);
+        }
+    }
+
+    private void notifyChangeIfNeeded() {
+        String summary = getSummary();
+        if (!TextUtils.equals(mSummary, summary)) {
+            mSummary = summary;
+            if (mListener != null) {
+                mListener.onSummaryChanged(summary);
+            }
+        }
+    }
+
+    private String getSummary() {
+        if (!mEnabled) {
+            return mContext.getString(R.string.bluetooth_disabled);
+        }
+        switch (mConnectionState) {
+            case BluetoothAdapter.STATE_CONNECTED:
+                return mContext.getString(R.string.bluetooth_connected);
+            case BluetoothAdapter.STATE_CONNECTING:
+                return mContext.getString(R.string.bluetooth_connecting);
+            case BluetoothAdapter.STATE_DISCONNECTING:
+                return mContext.getString(R.string.bluetooth_disconnecting);
+            default:
+                return mContext.getString(R.string.bluetooth_disconnected);
+        }
+    }
+
+    private void updateConnected() {
+        if (mBluetoothAdapter == null) {
+            return;
+        }
+        // Make sure our connection state is up to date.
+        int state = mBluetoothAdapter.getConnectionState();
+        if (state != mConnectionState) {
+            mConnectionState = state;
+            return;
+        }
+        final Collection<CachedBluetoothDevice> devices = getDevices();
+        if (devices == null) {
+            mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+            return;
+        }
+        if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
+            CachedBluetoothDevice connectedDevice = null;
+            for (CachedBluetoothDevice device : devices) {
+                if (device.isConnected()) {
+                    connectedDevice = device;
+                    break;
+                }
+            }
+            if (connectedDevice == null) {
+                // If somehow we think we are connected, but have no connected devices, we
+                // aren't connected.
+                mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+            }
+        }
+    }
+
+    private Collection<CachedBluetoothDevice> getDevices() {
+        return mBluetoothManager != null
+            ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+            : null;
+    }
+
+}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index b8ee1ff..fe0e1d2 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -20,7 +20,10 @@
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
+import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
+import com.android.settings.bluetooth.Utils;
 import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.deviceinfo.UsbBackend;
 import com.android.settings.nfc.NfcPreferenceController;
@@ -37,6 +40,7 @@
 
     private static final String TAG = "ConnectedDeviceFrag";
     private UsbModePreferenceController mUsbPrefController;
+    private BluetoothMasterSwitchPreferenceController mBluetoothPreferenceController;
 
     @Override
     public int getMetricsCategory() {
@@ -61,13 +65,19 @@
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
         final List<PreferenceController> controllers = new ArrayList<>();
+        final Lifecycle lifecycle = getLifecycle();
         final NfcPreferenceController nfcPreferenceController =
                 new NfcPreferenceController(context);
-        getLifecycle().addObserver(nfcPreferenceController);
+        lifecycle.addObserver(nfcPreferenceController);
         controllers.add(nfcPreferenceController);
         mUsbPrefController = new UsbModePreferenceController(context, new UsbBackend(context));
-        getLifecycle().addObserver(mUsbPrefController);
+        lifecycle.addObserver(mUsbPrefController);
         controllers.add(mUsbPrefController);
+        mBluetoothPreferenceController =
+            new BluetoothMasterSwitchPreferenceController(
+                context, Utils.getLocalBtManager(context));
+        lifecycle.addObserver(mBluetoothPreferenceController);
+        controllers.add(mBluetoothPreferenceController);
         return controllers;
     }
 
diff --git a/src/com/android/settings/inputmethod/InputAndGestureSettings.java b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
index 43d8b230b..4ad5af2 100644
--- a/src/com/android/settings/inputmethod/InputAndGestureSettings.java
+++ b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.UserHandle;
+import android.provider.SearchIndexableResource;
 import android.support.annotation.VisibleForTesting;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
@@ -31,9 +32,12 @@
 import com.android.settings.gestures.DoubleTwistPreferenceController;
 import com.android.settings.gestures.PickupGesturePreferenceController;
 import com.android.settings.gestures.SwipeToNotificationPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class InputAndGestureSettings extends DashboardFragment {
@@ -90,4 +94,19 @@
     void setAmbientDisplayConfig(AmbientDisplayConfiguration ambientConfig) {
         mAmbientDisplayConfig = ambientConfig;
     }
+
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    if (!FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
+                            .isEnabled()) {
+                        return null;
+                    }
+                    final SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.input_and_gesture;
+                    return Arrays.asList(sir);
+                }
+            };
 }
diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java
index cd6e562..b79a7f4 100644
--- a/src/com/android/settings/search/Index.java
+++ b/src/com/android/settings/search/Index.java
@@ -266,7 +266,7 @@
         StringBuilder sb = new StringBuilder();
 
         sb.append("SELECT ");
-        sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+        sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY);
         sb.append(" FROM ");
         sb.append(Tables.TABLE_SAVED_QUERIES);
 
@@ -274,7 +274,7 @@
             sb.append(" ORDER BY rowId DESC");
         } else {
             sb.append(" WHERE ");
-            sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+            sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY);
             sb.append(" LIKE ");
             sb.append("'");
             sb.append(query);
@@ -1299,8 +1299,8 @@
             final long now = new Date().getTime();
 
             final ContentValues values = new ContentValues();
-            values.put(IndexDatabaseHelper.SavedQueriesColums.QUERY, params[0]);
-            values.put(IndexDatabaseHelper.SavedQueriesColums.TIME_STAMP, now);
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, params[0]);
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
 
             final SQLiteDatabase database = getWritableDatabase();
             if (database == null) {
@@ -1312,7 +1312,7 @@
             try {
                 // First, delete all saved queries that are the same
                 database.delete(Tables.TABLE_SAVED_QUERIES,
-                        IndexDatabaseHelper.SavedQueriesColums.QUERY + " = ?",
+                        IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?",
                         new String[] { params[0] });
 
                 // Second, insert the saved query
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
index ba53e94..8de6c54 100644
--- a/src/com/android/settings/search/IndexDatabaseHelper.java
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -67,7 +67,7 @@
         String BUILD = "build";
     }
 
-    public interface SavedQueriesColums {
+    public interface SavedQueriesColumns {
         String QUERY = "query";
         String TIME_STAMP = "timestamp";
     }
@@ -127,9 +127,9 @@
     private static final String CREATE_SAVED_QUERIES_TABLE =
             "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
                     "(" +
-                    SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" +
+                    SavedQueriesColumns.QUERY + " VARCHAR(64) NOT NULL" +
                     ", " +
-                    SavedQueriesColums.TIME_STAMP + " INTEGER" +
+                    SavedQueriesColumns.TIME_STAMP + " INTEGER" +
                     ")";
 
     private static final String INSERT_BUILD_VERSION =
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 8a158cd..cb848d8 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -55,6 +55,7 @@
 import com.android.settings.gestures.PickupGestureSettings;
 import com.android.settings.gestures.SwipeToNotificationSettings;
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
+import com.android.settings.inputmethod.InputAndGestureSettings;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
 import com.android.settings.inputmethod.PhysicalKeyboardFragment;
 import com.android.settings.inputmethod.VirtualKeyboardFragment;
@@ -136,6 +137,7 @@
         addIndex(DoubleTwistGestureSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_gestures);
         addIndex(SwipeToNotificationSettings.class, NO_DATA_RES_ID,
                 R.drawable.ic_settings_gestures);
+        addIndex(InputAndGestureSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
         addIndex(LocationSettings.class, R.xml.location_settings, R.drawable.ic_settings_location);
         addIndex(ScanningSettings.class, R.xml.location_scanning, R.drawable.ic_settings_location);
         addIndex(SecuritySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_security);
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
index 449e52c..14735bd 100644
--- a/src/com/android/settings/search2/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -41,11 +42,14 @@
 
     private static final int NAME_NO_MATCH = -1;
     private static final int NAME_EXACT_MATCH = 0;
+    private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
+            .addCategory(Intent.CATEGORY_LAUNCHER);
 
     private final String mQuery;
     private final UserManager mUserManager;
     private final PackageManagerWrapper mPackageManager;
 
+
     public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
             String query) {
         super(context);
@@ -67,7 +71,7 @@
                                     | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
                             user.id);
             for (ApplicationInfo info : apps) {
-                if (info.isSystemApp()) {
+                if (!shouldIncludeAsCandidate(info, user)) {
                     continue;
                 }
                 final CharSequence label = info.loadLabel(pm);
@@ -91,6 +95,22 @@
         return results;
     }
 
+    private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
+        if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+                || (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            return true;
+        }
+        final Intent launchIntent = new Intent(LAUNCHER_PROBE)
+                .setPackage(info.packageName);
+        final List<ResolveInfo> intents = mPackageManager.queryIntentActivitiesAsUser(
+                launchIntent,
+                PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                user.id);
+        return intents != null && intents.size() != 0;
+    }
+
     @Override
     protected void onDiscardResult(List<SearchResult> result) {
 
diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java
index 3842def..4294234 100644
--- a/src/com/android/settings/search2/ResultPayload.java
+++ b/src/com/android/settings/search2/ResultPayload.java
@@ -29,7 +29,7 @@
 public abstract class ResultPayload implements Parcelable {
 
     @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH,
-            PayloadType.INTENT})
+            PayloadType.INTENT, PayloadType.SAVED_QUERY})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PayloadType {
         /**
@@ -46,6 +46,11 @@
          * Result is a inline widget, using a toggle widget as UI.
          */
         int INLINE_SWITCH = 2;
+
+        /**
+         * Result is a recently saved query.
+         */
+        int SAVED_QUERY = 3;
     }
 
     @IntDef({SettingsSource.UNKNOWN, SettingsSource.SYSTEM, SettingsSource.SECURE,
@@ -59,5 +64,6 @@
     }
 
 
-    @ResultPayload.PayloadType public abstract int getType();
+    @ResultPayload.PayloadType
+    public abstract int getType();
 }
diff --git a/src/com/android/settings/search2/SavedQueryLoader.java b/src/com/android/settings/search2/SavedQueryLoader.java
new file mode 100644
index 0000000..b034b44
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryLoader.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.SavedQueriesColumns;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader for recently searched queries.
+ */
+public class SavedQueryLoader extends AsyncLoader<List<SearchResult>> {
+
+    // Max number of proposed suggestions
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int MAX_PROPOSED_SUGGESTIONS = 5;
+
+    private final SQLiteDatabase mDatabase;
+
+    public SavedQueryLoader(Context context) {
+        super(context);
+        mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
+    }
+
+    @Override
+    protected void onDiscardResult(List<SearchResult> result) {
+
+    }
+
+    @Override
+    public List<SearchResult> loadInBackground() {
+        Cursor cursor = mDatabase.query(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES /* table */,
+                new String[]{SavedQueriesColumns.QUERY} /* columns */,
+                null /* selection */,
+                null /* selectionArgs */,
+                null /* groupBy */,
+                null /* having */,
+                "rowId DESC" /* orderBy */,
+                String.valueOf(MAX_PROPOSED_SUGGESTIONS) /* limit */);
+        return convertCursorToResult(cursor);
+    }
+
+    private List<SearchResult> convertCursorToResult(Cursor cursor) {
+        final List<SearchResult> results = new ArrayList<>();
+        while (cursor.moveToNext()) {
+            final SavedQueryPayload payload = new SavedQueryPayload(
+                    cursor.getString(cursor.getColumnIndex(SavedQueriesColumns.QUERY)));
+            results.add(new SearchResult.Builder()
+                    .addTitle(payload.query)
+                    .addPayload(payload)
+                    .build());
+        }
+        return results;
+    }
+}
diff --git a/src/com/android/settings/search2/SavedQueryPayload.java b/src/com/android/settings/search2/SavedQueryPayload.java
new file mode 100644
index 0000000..6316894
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryPayload.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+import android.os.Parcel;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * {@link ResultPayload} for saved query.
+ */
+public class SavedQueryPayload extends ResultPayload {
+
+    public final String query;
+
+    public SavedQueryPayload(String query) {
+        this.query = query;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    SavedQueryPayload(Parcel in) {
+        query = in.readString();
+    }
+
+    @Override
+    public int getType() {
+        return PayloadType.SAVED_QUERY;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(query);
+    }
+
+    public static final Creator<SavedQueryPayload> CREATOR = new Creator<SavedQueryPayload>() {
+        @Override
+        public SavedQueryPayload createFromParcel(Parcel in) {
+            return new SavedQueryPayload(in);
+        }
+
+        @Override
+        public SavedQueryPayload[] newArray(int size) {
+            return new SavedQueryPayload[size];
+        }
+    };
+}
diff --git a/src/com/android/settings/search2/SavedQueryRecorder.java b/src/com/android/settings/search2/SavedQueryRecorder.java
new file mode 100644
index 0000000..e2325e8
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryRecorder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.utils.AsyncLoader;
+
+import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES;
+
+/**
+ * A background task to update saved queries.
+ */
+public class SavedQueryRecorder extends AsyncLoader<Void> {
+
+    private static final String LOG_TAG = "SavedQueryRecorder";
+
+    // Max number of saved search queries (who will be used for proposing suggestions)
+    private static long MAX_SAVED_SEARCH_QUERY = 64;
+
+    private final String mQuery;
+
+    public SavedQueryRecorder(Context context, String query) {
+        super(context);
+        mQuery = query;
+    }
+
+    @Override
+    protected void onDiscardResult(Void result) {
+
+    }
+
+    @Override
+    public Void loadInBackground() {
+        final long now = System.currentTimeMillis();
+
+        final ContentValues values = new ContentValues();
+        values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, mQuery);
+        values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
+
+        final SQLiteDatabase database = getWritableDatabase();
+        if (database == null) {
+            return null;
+        }
+
+        long lastInsertedRowId;
+        try {
+            // First, delete all saved queries that are the same
+            database.delete(TABLE_SAVED_QUERIES,
+                    IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?",
+                    new String[]{mQuery});
+
+            // Second, insert the saved query
+            lastInsertedRowId = database.insertOrThrow(TABLE_SAVED_QUERIES, null, values);
+
+            // Last, remove "old" saved queries
+            final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
+            if (delta > 0) {
+                int count = database.delete(TABLE_SAVED_QUERIES,
+                        "rowId <= ?",
+                        new String[]{Long.toString(delta)});
+                Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
+            }
+        } catch (Exception e) {
+            Log.d(LOG_TAG, "Cannot update saved Search queries", e);
+        }
+        return null;
+    }
+
+    private SQLiteDatabase getWritableDatabase() {
+        try {
+            return IndexDatabaseHelper.getInstance(getContext()).getWritableDatabase();
+        } catch (SQLiteException e) {
+            Log.e(LOG_TAG, "Cannot open writable database", e);
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SavedQueryViewHolder.java b/src/com/android/settings/search2/SavedQueryViewHolder.java
new file mode 100644
index 0000000..a32ed05
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryViewHolder.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+import android.view.View;
+import android.widget.TextView;
+
+public class SavedQueryViewHolder extends SearchViewHolder {
+
+    public final TextView titleView;
+
+    public SavedQueryViewHolder(View view) {
+        super(view);
+        titleView = (TextView) view.findViewById(android.R.id.title);
+    }
+
+    @Override
+    public void onBind(SearchFragment fragment, SearchResult result) {
+        titleView.setText(result.title);
+        itemView.setOnClickListener(v -> {
+            fragment.onSavedQueryClicked(result.title);
+        });
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 8a616a7..91a1444 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -48,6 +48,11 @@
     InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
 
     /**
+     * Returns a new loader to get all recently saved queries search terms.
+     */
+    SavedQueryLoader getSavedQueryLoader(Context context);
+
+    /**
      * Returns the manager for indexing Settings data.
      */
     DatabaseIndexingManager getIndexingManager(Context context);
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index a76d905..5d62412 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -73,6 +73,11 @@
     }
 
     @Override
+    public SavedQueryLoader getSavedQueryLoader(Context context) {
+        return new SavedQueryLoader(context);
+    }
+
+    @Override
     public DatabaseIndexingManager getIndexingManager(Context context) {
         if (mDatabaseIndexingManager == null) {
             mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext(),
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
index e26f5ed..b688a45 100644
--- a/src/com/android/settings/search2/SearchFragment.java
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -52,8 +52,9 @@
     private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count";
 
     // Loader IDs
-    private static final int LOADER_ID_DATABASE = 0;
-    private static final int LOADER_ID_INSTALLED_APPS = 1;
+    private static final int LOADER_ID_RECENTS = 0;
+    private static final int LOADER_ID_DATABASE = 1;
+    private static final int LOADER_ID_INSTALLED_APPS = 2;
 
     // Logging
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -61,6 +62,10 @@
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     String mQuery;
+
+    private final SaveQueryRecorderCallback mSaveQueryRecorderCallback =
+            new SaveQueryRecorderCallback();
+
     private boolean mNeverEnteredQuery = true;
     private int mResultClickCount;
     private MetricsFeatureProvider mMetricsFeatureProvider;
@@ -68,6 +73,7 @@
 
     private SearchResultsAdapter mSearchAdapter;
     private RecyclerView mResultsRecyclerView;
+    private SearchView mSearchView;
 
     @Override
     public int getMetricsCategory() {
@@ -86,18 +92,21 @@
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
         mSearchAdapter = new SearchResultsAdapter(this);
+        final LoaderManager loaderManager = getLoaderManager();
         if (savedInstanceState != null) {
             mQuery = savedInstanceState.getString(STATE_QUERY);
             mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
             mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT);
-            final LoaderManager loaderManager = getLoaderManager();
             loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
             loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
+        } else {
+            loaderManager.initLoader(LOADER_ID_RECENTS, null, this);
         }
 
         final Activity activity = getActivity();
         final ActionBar actionBar = activity.getActionBar();
-        actionBar.setCustomView(makeSearchView(actionBar, mQuery));
+        mSearchView = makeSearchView(actionBar, mQuery);
+        actionBar.setCustomView(mSearchView);
         actionBar.setDisplayShowCustomEnabled(true);
         actionBar.setDisplayShowTitleEnabled(false);
 
@@ -151,7 +160,10 @@
         mSearchAdapter.clearResults();
 
         if (TextUtils.isEmpty(mQuery)) {
-            getLoaderManager().destroyLoader(LOADER_ID_DATABASE);
+            final LoaderManager loaderManager = getLoaderManager();
+            loaderManager.destroyLoader(LOADER_ID_DATABASE);
+            loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
+            loaderManager.restartLoader(LOADER_ID_RECENTS, null /* args */, this /* callback */);
         } else {
             restartLoaders();
         }
@@ -161,6 +173,10 @@
 
     @Override
     public boolean onQueryTextSubmit(String query) {
+        // Save submitted query.
+        getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null,
+                mSaveQueryRecorderCallback);
+
         return true;
     }
 
@@ -173,6 +189,8 @@
                 return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
             case LOADER_ID_INSTALLED_APPS:
                 return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
+            case LOADER_ID_RECENTS:
+                return mSearchFeatureProvider.getSavedQueryLoader(activity);
             default:
                 return null;
         }
@@ -191,6 +209,12 @@
         mResultClickCount++;
     }
 
+    public void onSavedQueryClicked(CharSequence query) {
+        final String queryString = query.toString();
+        mSearchView.setQuery(queryString, false /* submit */);
+        onQueryTextChange(queryString);
+    }
+
     private void restartLoaders() {
         final LoaderManager loaderManager = getLoaderManager();
         loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
@@ -207,4 +231,25 @@
         searchView.setLayoutParams(lp);
         return searchView;
     }
+
+    private class SaveQueryRecorderCallback implements LoaderManager.LoaderCallbacks<Void> {
+        // TODO: make a generic background task manager to handle one-off tasks like this one.
+
+        private static final int LOADER_ID_SAVE_QUERY_TASK = 0;
+
+        @Override
+        public Loader<Void> onCreateLoader(int id, Bundle args) {
+            return new SavedQueryRecorder(getActivity(), mQuery);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Void> loader, Void data) {
+
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Void> loader) {
+
+        }
+    }
 }
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
index c318b41..999a485 100644
--- a/src/com/android/settings/search2/SearchResultsAdapter.java
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -49,13 +49,16 @@
         final Context context = parent.getContext();
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view;
-        switch(viewType) {
+        switch (viewType) {
             case PayloadType.INTENT:
                 view = inflater.inflate(R.layout.search_intent_item, parent, false);
                 return new IntentSearchViewHolder(view);
             case PayloadType.INLINE_SWITCH:
                 view = inflater.inflate(R.layout.search_inline_switch_item, parent, false);
                 return new InlineSwitchViewHolder(view, context);
+            case PayloadType.SAVED_QUERY:
+                view = inflater.inflate(R.layout.search_saved_query_item, parent, false);
+                return new SavedQueryViewHolder(view);
             default:
                 return null;
         }
diff --git a/src/com/android/settings/widget/MasterSwitchController.java b/src/com/android/settings/widget/MasterSwitchController.java
new file mode 100644
index 0000000..53e5fe7
--- /dev/null
+++ b/src/com/android/settings/widget/MasterSwitchController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.support.v7.preference.Preference;
+
+/*
+ * The switch controller that is used to update the switch widget in the MasterSwitchPreference
+ * layout.
+ */
+public class MasterSwitchController extends SwitchWidgetController implements
+    Preference.OnPreferenceChangeListener {
+
+    private final MasterSwitchPreference mPreference;
+
+    public MasterSwitchController(MasterSwitchPreference preference) {
+        mPreference = preference;
+    }
+
+    @Override
+    public void updateTitle(boolean isChecked) {
+    }
+
+    @Override
+    public void startListening() {
+        mPreference.setOnPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void stopListening() {
+        mPreference.setOnPreferenceChangeListener(null);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mPreference.setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mPreference.isChecked();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mPreference.setSwitchEnabled(enabled);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mListener != null) {
+            return mListener.onSwitchToggled((Boolean) newValue);
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/settings/widget/MasterSwitchPreference.java b/src/com/android/settings/widget/MasterSwitchPreference.java
new file mode 100644
index 0000000..8130ca5
--- /dev/null
+++ b/src/com/android/settings/widget/MasterSwitchPreference.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import com.android.settings.R;
+
+/**
+ * A custom preference that provides inline switch toggle. It has a mandatory field for title, and
+ * optional fields for icon and sub-text.
+ */
+public class MasterSwitchPreference extends Preference {
+
+    private Switch mSwitch;
+    private boolean mChecked;
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mSwitch = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        if (mSwitch != null) {
+            mSwitch.setChecked(mChecked);
+            mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                @Override
+                public void onCheckedChanged(CompoundButton button, boolean isChecked) {
+                    if (!callChangeListener(isChecked)) {
+                        button.setChecked(!isChecked);
+                    } else {
+                        persistBoolean(isChecked);
+                        mChecked = isChecked;
+                    }
+                }
+            });
+        }
+    }
+
+    public boolean isChecked() {
+        return isEnabled() && mChecked;
+    }
+
+    public void setChecked(boolean checked) {
+        mChecked = checked;
+        if (mSwitch != null) {
+            mSwitch.setChecked(checked);
+        }
+    }
+
+    public boolean isSwitchEnabled() {
+        return isEnabled() && mSwitch != null && mSwitch.isEnabled();
+    }
+
+    public void setSwitchEnabled(boolean enabled) {
+        if (mSwitch != null) {
+            mSwitch.setEnabled(enabled);
+        }
+    }
+
+    private void init() {
+        setWidgetLayoutResource(R.layout.preference_widget_master_switch);
+    }
+}
diff --git a/src/com/android/settings/widget/SwitchBarController.java b/src/com/android/settings/widget/SwitchBarController.java
new file mode 100644
index 0000000..70fd7ba
--- /dev/null
+++ b/src/com/android/settings/widget/SwitchBarController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.widget.Switch;
+
+/*
+ * The switch controller that is used to update the switch widget in the SwitchBar layout.
+ */
+public class SwitchBarController extends SwitchWidgetController implements
+    SwitchBar.OnSwitchChangeListener {
+
+    private final SwitchBar mSwitchBar;
+    private final Switch mSwitch;
+
+    public SwitchBarController(SwitchBar switchBar) {
+        mSwitchBar = switchBar;
+        mSwitch = switchBar.getSwitch();
+    }
+
+    @Override
+    public void setupView() {
+        mSwitchBar.show();
+    }
+
+    @Override
+    public void teardownView() {
+        mSwitchBar.hide();
+    }
+
+    @Override
+    public void updateTitle(boolean isChecked) {
+        mSwitchBar.setTextViewLabel(isChecked);
+    }
+
+    @Override
+    public void startListening() {
+        mSwitchBar.addOnSwitchChangeListener(this);
+    }
+
+    @Override
+    public void stopListening() {
+        mSwitchBar.removeOnSwitchChangeListener(this);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mSwitch.setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mSwitch.isChecked();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mSwitch.setEnabled(enabled);
+    }
+
+    @Override
+    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+        if (mListener != null) {
+            mListener.onSwitchToggled(isChecked);
+        }
+    }
+}
diff --git a/src/com/android/settings/widget/SwitchWidgetController.java b/src/com/android/settings/widget/SwitchWidgetController.java
new file mode 100644
index 0000000..c5a8c87
--- /dev/null
+++ b/src/com/android/settings/widget/SwitchWidgetController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+/*
+ * A controller class for general switch widget handling. We have different containers that provide
+ * different forms of switch layout. Provide a centralized control for updating the switch widget.
+ */
+public abstract class SwitchWidgetController {
+
+    protected OnSwitchChangeListener mListener;
+
+    public interface OnSwitchChangeListener {
+        /**
+         * Called when the checked state of the Switch has changed.
+         *
+         * @param isChecked  The new checked state of switchView.
+         */
+        boolean onSwitchToggled(boolean isChecked);
+    }
+
+    public void setupView() {
+    }
+
+    public void teardownView() {
+    }
+
+    public void setListener(OnSwitchChangeListener listener) {
+        mListener = listener;
+    }
+
+    public abstract void updateTitle(boolean isChecked);
+
+    public abstract void startListening();
+
+    public abstract void stopListening();
+
+    public abstract void setChecked(boolean checked);
+
+    public abstract boolean isChecked();
+
+    public abstract void setEnabled(boolean enabled);
+
+}
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index 81adf8b..a178596 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -89,4 +89,5 @@
 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment
 com.android.settings.applications.ConvertToFbe
 com.android.settings.localepicker.LocaleListEditor
-com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
\ No newline at end of file
+com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
+com.android.settings.applications.ExternalSourcesDetails
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java
new file mode 100644
index 0000000..0e39c5d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.MasterSwitchPreference;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothMasterSwitchPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LocalBluetoothManager mBluetoothManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private MasterSwitchPreference mPreference;
+
+    private Context mContext;
+    private BluetoothMasterSwitchPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application.getApplicationContext();
+        mController = new BluetoothMasterSwitchPreferenceController(mContext, mBluetoothManager);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+    }
+
+    @Test
+    public void isAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void onResume_shouldRegisterCallback() {
+        mController.onResume();
+
+        verify(mBluetoothManager.getEventManager()).registerCallback(any(BluetoothCallback.class));
+    }
+
+    @Test
+    public void onPause_shouldUnregisterCallback() {
+        mController.onPause();
+
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(
+            any(BluetoothCallback.class));
+    }
+
+    @Test
+    public void onStart_shouldRegisterPreferenceChangeListener() {
+        mController.displayPreference(mScreen);
+        mController.onStart();
+
+        verify(mPreference).setOnPreferenceChangeListener(any(OnPreferenceChangeListener.class));
+    }
+
+    @Test
+    public void onStop_shouldRegisterPreferenceChangeListener() {
+        mController.displayPreference(mScreen);
+        mController.onStart();
+
+        mController.onStop();
+
+        verify(mPreference).setOnPreferenceChangeListener(null);
+    }
+
+    @Test
+    public void onSummaryUpdated_shouldUpdatePreferenceSummary() {
+        mController.displayPreference(mScreen);
+
+        mController.onSummaryChanged("test summary");
+
+        verify(mPreference).setSummary("test summary");
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
index 7ac7cb1..2822b1e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
@@ -16,14 +16,11 @@
 
 package com.android.settings.bluetooth;
 
-import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
-import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.SummaryLoader;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import org.junit.Before;
@@ -34,15 +31,10 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowBluetoothAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -67,102 +59,27 @@
     }
 
     @Test
-    public void setListening_shouldUpdateSummary() {
+    public void setListening_shouldRegister() {
         mSummaryProvider.setListening(true);
 
-        verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryProvider);
-        verify(mSummaryLoader).setSummary(eq(mSummaryProvider), anyString());
+        verify(mBluetoothManager.getEventManager()).registerCallback(
+            mSummaryProvider.mSummaryHelper);
     }
 
     @Test
     public void setNotListening_shouldUnregister() {
         mSummaryProvider.setListening(false);
 
-        verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryProvider);
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(
+            mSummaryProvider.mSummaryHelper);
     }
 
     @Test
-    public void updateSummary_btDisabled_shouldShowDisabledMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().disable();
-        mSummaryProvider.setListening(true);
+    public void onSummaryChanged_shouldSetSummary() {
+        final String summary = "Bluetooth summary";
+        mSummaryProvider.onSummaryChanged(summary);
 
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disabled));
-    }
-
-    @Test
-    public void updateSummary_btEnabled_noDevice_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.setListening(true);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-    @Test
-    public void updateState_btEnabled_noDevice_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-    @Test
-    public void updateState_btDisabled_shouldShowDisabledMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_OFF);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disabled));
-    }
-
-    @Test
-    public void updateConnectionState_disconnected_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_DISCONNECTED);
-
-        verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-
-    @Test
-    public void updateConnectionState_connected_shouldShowConnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_CONNECTED);
-        final List<CachedBluetoothDevice> devices = new ArrayList<>();
-        devices.add(mock(CachedBluetoothDevice.class));
-        when(devices.get(0).isConnected()).thenReturn(true);
-        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
-                .thenReturn(devices);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_CONNECTED);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_connected));
-    }
-
-    @Test
-    public void updateConnectionState_inconsistentState_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_CONNECTED);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_CONNECTED);
-
-        verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
+        verify(mSummaryLoader).setSummary(mSummaryProvider, summary);
     }
 
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java
new file mode 100644
index 0000000..f25e6d9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowBluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothSummaryHelperTest {
+
+    private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LocalBluetoothManager mBluetoothManager;
+    @Mock
+    private LocalBluetoothAdapter mBtAdapter;
+
+    private BluetoothSummaryHelper mHelper;
+    @Mock
+    private SummaryListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter);
+        when(mBtAdapter.isEnabled()).thenReturn(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+        mContext = RuntimeEnvironment.application.getApplicationContext();
+        mHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+        mHelper.setOnSummaryChangeListener(mListener);
+    }
+
+    @Test
+    public void setListening_shouldRegisterListener() {
+        mHelper.setListening(true);
+
+        verify(mBluetoothManager.getEventManager()).registerCallback(mHelper);
+    }
+
+    @Test
+    public void setNotListening_shouldUnregisterListener() {
+        mHelper.setListening(false);
+
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(mHelper);
+    }
+
+    @Test
+    public void setListening_shouldSendSummaryChange() {
+        mHelper.setListening(true);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
+        final List<CachedBluetoothDevice> devices = new ArrayList<>();
+        devices.add(mock(CachedBluetoothDevice.class));
+        when(devices.get(0).isConnected()).thenReturn(true);
+        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
+            .thenReturn(devices);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+        mHelper.setListening(true);
+
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
+        mHelper.setListening(true);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
+        mHelper.setListening(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTING);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connecting));
+    }
+
+    @Test
+    public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
+        mHelper.setListening(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTING);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_DISCONNECTING);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnecting));
+    }
+
+    private class SummaryListener implements BluetoothSummaryHelper.OnSummaryChangeListener {
+        String summary;
+
+        @Override
+        public void onSummaryChanged(String summary) {
+            this.summary = summary;
+        }
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
index 8b363b0..3d469dd 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
@@ -21,10 +21,12 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.SearchIndexableResource;
+
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.search2.DatabaseIndexingManager;
+import com.android.settings.testutils.DatabaseTestUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -33,7 +35,6 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -85,15 +86,7 @@
 
     @After
     public void cleanUp() {
-        Field instance;
-        Class clazz = IndexDatabaseHelper.class;
-        try {
-            instance = clazz.getDeclaredField("sSingleton");
-            instance.setAccessible(true);
-            instance.set(null, null);
-        } catch (Exception e) {
-            throw new RuntimeException();
-        }
+        DatabaseTestUtils.clearDb();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index c592aef..2b29a16 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -20,10 +20,13 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
+
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.search2.DatabaseIndexingUtils;
 import com.android.settings.search2.DatabaseResultLoader;
+import com.android.settings.testutils.DatabaseTestUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,8 +34,6 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import java.lang.reflect.Field;
-
 import static com.google.common.truth.Truth.assertThat;
 
 @RunWith(SettingsRobolectricTestRunner.class)
@@ -53,15 +54,7 @@
 
     @After
     public void cleanUp() {
-        Field instance;
-        Class clazz = IndexDatabaseHelper.class;
-        try {
-            instance = clazz.getDeclaredField("sSingleton");
-            instance.setAccessible(true);
-            instance.set(null, null);
-        } catch (Exception e) {
-            throw new RuntimeException();
-        }
+        DatabaseTestUtils.clearDb();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index 8fde73d..4f62a9e 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -17,6 +17,8 @@
 package com.android.settings.search2;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 
@@ -38,8 +40,11 @@
 import java.util.List;
 
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
@@ -95,6 +100,55 @@
     }
 
     @Test
+    public void query_matchingQuery_shouldReturnSystemAppUpdates() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
+                                0 /* targetSdkVersion */)));
+        final String query = "app";
+
+        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
+        final List<ResolveInfo> list = mock(List.class);
+        when(list.size()).thenReturn(1);
+        when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
+                any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(list);
+
+        final String query = "app";
+
+        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void query_matchingQuery_shouldNOtReturnSystemAppIfNotLaunchable() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
+        when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
+                any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(null);
+
+        final String query = "app";
+
+        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+        assertThat(mLoader.loadInBackground()).isEmpty();
+    }
+
+    @Test
     public void query_matchingQuery_shouldRankBasedOnSimilarity() {
         final String query = "app";
 
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
new file mode 100644
index 0000000..d975f0c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.testutils.DatabaseTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryLoaderTest {
+
+    private Context mContext;
+    private SQLiteDatabase mDb;
+    private SavedQueryLoader mLoader;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        mLoader = new SavedQueryLoader(mContext);
+        setUpDb();
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void loadInBackground_shouldReturnSavedQueries() {
+        final List<SearchResult> results = mLoader.loadInBackground();
+        assertThat(results.size()).isEqualTo(SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS);
+        for (SearchResult result : results) {
+            assertThat(result.viewType).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
+        }
+    }
+
+    private void setUpDb() {
+        final long now = System.currentTimeMillis();
+        for (int i = 0; i < SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS + 2; i++) {
+            ContentValues values = new ContentValues();
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, String.valueOf(i));
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
+            mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES, null, values);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java
new file mode 100644
index 0000000..daa6d5e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryPayloadTest {
+
+    private SavedQueryPayload mPayload;
+
+    @Test
+    public void getType_shouldBeSavedQueryType() {
+        mPayload = new SavedQueryPayload("Test");
+        assertThat(mPayload.getType()).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
new file mode 100644
index 0000000..c56ecce
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search2;
+
+
+import android.content.Context;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.DatabaseTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryRecorderTest {
+
+    private Context mContext;
+    private SavedQueryRecorder mRecorder;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void canSaveQueryToDb() {
+        final String query = "test";
+        mRecorder = new SavedQueryRecorder(mContext, query);
+
+        mRecorder.loadInBackground();
+
+        final SavedQueryLoader loader = new SavedQueryLoader(mContext);
+        List<SearchResult> results = loader.loadInBackground();
+
+        assertThat(results.size()).isEqualTo(1);
+        assertThat(results.get(0).title).isEqualTo(query);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
index d97360d..7a0bb54 100644
--- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -52,6 +53,9 @@
     private DatabaseResultLoader mDatabaseResultLoader;
     @Mock
     private InstalledAppResultLoader mInstalledAppResultLoader;
+    @Mock
+    private SavedQueryLoader mSavedQueryLoader;
+
     private FakeFeatureFactory mFeatureFactory;
 
     @Before
@@ -65,6 +69,8 @@
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+                .thenReturn(mSavedQueryLoader);
     }
 
     @Test
@@ -115,6 +121,27 @@
     }
 
     @Test
+    public void queryTextChangeToEmpty_shouldTriggerSavedQueryLoader() {
+        ActivityController<SearchActivity> activityController =
+                Robolectric.buildActivity(SearchActivity.class);
+        activityController.setup();
+        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
+                .findFragmentById(R.id.main_content);
+
+        fragment.onQueryTextChange("");
+        activityController.get().onBackPressed();
+        activityController.pause().stop().destroy();
+
+        verify(mFeatureFactory.searchFeatureProvider, never())
+                .getDatabaseSearchLoader(any(Context.class), anyString());
+        verify(mFeatureFactory.searchFeatureProvider, never())
+                .getInstalledAppSearchLoader(any(Context.class), anyString());
+        // Saved query loaded 2 times: fragment start, and query change to empty.
+        verify(mFeatureFactory.searchFeatureProvider, times(2))
+                .getSavedQueryLoader(any(Context.class));
+    }
+
+    @Test
     public void updateIndex_TriggerOnCreate() {
         ActivityController<SearchActivity> activityController =
                 Robolectric.buildActivity(SearchActivity.class);
diff --git a/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
new file mode 100644
index 0000000..8fbe1c9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.testutils;
+
+import com.android.settings.search.IndexDatabaseHelper;
+
+import java.lang.reflect.Field;
+
+public class DatabaseTestUtils {
+
+    public static void clearDb() {
+        Field instance;
+        Class clazz = IndexDatabaseHelper.class;
+        try {
+            instance = clazz.getDeclaredField("sSingleton");
+            instance.setAccessible(true);
+            instance.set(null, null);
+        } catch (Exception e) {
+            throw new RuntimeException();
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java
new file mode 100644
index 0000000..1dce599
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.view.LayoutInflater;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.Switch;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MasterSwitchPreferenceTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ShadowApplication.getInstance().getApplicationContext();
+    }
+
+    @Test
+    public void createNewPreference_shouldSetLayout() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+
+        assertThat(preference.getWidgetLayoutResource()).isEqualTo(
+            R.layout.preference_widget_master_switch);
+    }
+
+    @Test
+    public void setChecked_shouldUpdateButtonCheckedState() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(LayoutInflater.from(mContext)
+            .inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        preference.onBindViewHolder(holder);
+
+        preference.setChecked(true);
+        assertThat(toggle.isChecked()).isTrue();
+
+        preference.setChecked(false);
+        assertThat(toggle.isChecked()).isFalse();
+    }
+
+    @Test
+    public void setSwitchEnabled_shouldUpdateButtonEnabledState() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        preference.onBindViewHolder(holder);
+
+        preference.setSwitchEnabled(true);
+        assertThat(toggle.isEnabled()).isTrue();
+
+        preference.setSwitchEnabled(false);
+        assertThat(toggle.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void toggleButtonOn_shouldNotifyChecked() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
+        preference.setOnPreferenceChangeListener(listener);
+        preference.onBindViewHolder(holder);
+
+        toggle.setChecked(true);
+        verify(listener).onPreferenceChange(preference, true);
+    }
+
+    @Test
+    public void toggleButtonOff_shouldNotifyUnchecked() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
+        preference.setChecked(true);
+        preference.setOnPreferenceChangeListener(listener);
+        preference.onBindViewHolder(holder);
+
+        toggle.setChecked(false);
+        verify(listener).onPreferenceChange(preference, false);
+    }
+}