Implement default assist app setting

- Add "None" support to AppListPreference
- Add DefaultAssistPreference to manage assist;
- Add AppListPreferenceWithSettings to show a settings icon;
- Implement DefaultAssistPreference based on AppListPreferenceWithSettings;
- Move voice input settings into ManageAssist and implement it
  based on AppListPreferenceWithSettings;

Bug:20210110
Change-Id: If283b8b55a46b428ecfa6e45dc2123292b1d4302
diff --git a/src/com/android/settings/AppListPreference.java b/src/com/android/settings/AppListPreference.java
index 3cc91cd..d5e0c3b 100644
--- a/src/com/android/settings/AppListPreference.java
+++ b/src/com/android/settings/AppListPreference.java
@@ -35,13 +35,20 @@
 import android.widget.ListAdapter;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Extends ListPreference to allow us to show the icons for a given list of applications. We do this
  * because the names of applications are very similar and the user may not be able to determine what
  * app they are selecting without an icon.
  */
 public class AppListPreference extends ListPreference {
+
+    public static final String ITEM_NONE_VALUE = "";
+
     private Drawable[] mEntryDrawables;
+    private boolean mShowItemNone = false;
 
     public class AppArrayAdapter extends ArrayAdapter<CharSequence> {
         private Drawable[] mImageDrawables = null;
@@ -78,38 +85,45 @@
         super(context, attrs);
     }
 
+    public void setShowItemNone(boolean showItemNone) {
+        mShowItemNone = showItemNone;
+    }
+
     public void setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName) {
         // Look up all package names in PackageManager. Skip ones we can't find.
-        int foundPackages = 0;
         PackageManager pm = getContext().getPackageManager();
-        ApplicationInfo[] appInfos = new ApplicationInfo[packageNames.length];
+        final int entryCount = packageNames.length + (mShowItemNone ? 1 : 0);
+        List<CharSequence> applicationNames = new ArrayList<>(entryCount);
+        List<CharSequence> validatedPackageNames = new ArrayList<>(entryCount);
+        List<Drawable> entryDrawables = new ArrayList<>(entryCount);
+        int selectedIndex = -1;
         for (int i = 0; i < packageNames.length; i++) {
             try {
-                appInfos[i] = pm.getApplicationInfo(packageNames[i].toString(), 0);
-                foundPackages++;
+                ApplicationInfo appInfo = pm.getApplicationInfo(packageNames[i].toString(), 0);
+                applicationNames.add(appInfo.loadLabel(pm));
+                validatedPackageNames.add(appInfo.packageName);
+                entryDrawables.add(appInfo.loadIcon(pm));
+                if (defaultPackageName != null &&
+                        appInfo.packageName.contentEquals(defaultPackageName)) {
+                    selectedIndex = i;
+                }
             } catch (NameNotFoundException e) {
-                // Leave appInfos[i] uninitialized; it will be skipped in the list.
+                // Skip unknown packages.
             }
         }
 
-        // Show the label and icon for each application package.
-        CharSequence[] applicationNames = new CharSequence[foundPackages];
-        mEntryDrawables = new Drawable[foundPackages];
-        int index = 0;
-        int selectedIndex = -1;
-        for (ApplicationInfo appInfo : appInfos) {
-            if (appInfo != null) {
-                applicationNames[index] = appInfo.loadLabel(pm);
-                mEntryDrawables[index] = appInfo.loadIcon(pm);
-                if (defaultPackageName != null &&
-                        appInfo.packageName.contentEquals(defaultPackageName)) {
-                    selectedIndex = index;
-                }
-                index++;
-            }
+        if (mShowItemNone) {
+            applicationNames.add(
+                    getContext().getResources().getText(R.string.app_list_preference_none));
+            validatedPackageNames.add(ITEM_NONE_VALUE);
+            entryDrawables.add(getContext().getDrawable(R.drawable.ic_remove_circle));
         }
-        setEntries(applicationNames);
-        setEntryValues(packageNames);
+
+        setEntries(applicationNames.toArray(new CharSequence[applicationNames.size()]));
+        setEntryValues(
+                validatedPackageNames.toArray(new CharSequence[validatedPackageNames.size()]));
+        mEntryDrawables = entryDrawables.toArray(new Drawable[entryDrawables.size()]);
+
         if (selectedIndex != -1) {
             setValueIndex(selectedIndex);
         } else {
@@ -117,25 +131,32 @@
         }
     }
 
+    protected ListAdapter createListAdapter() {
+        final String selectedValue = getValue();
+        final boolean selectedNone = selectedValue == null ||
+                (mShowItemNone && selectedValue.contentEquals(ITEM_NONE_VALUE));
+        int selectedIndex = selectedNone ? -1 : findIndexOfValue(selectedValue);
+        return new AppArrayAdapter(getContext(),
+            R.layout.app_preference_item, getEntries(), mEntryDrawables, selectedIndex);
+    }
+
     @Override
     protected void onPrepareDialogBuilder(Builder builder) {
-        int selectedIndex = findIndexOfValue(getValue());
-        ListAdapter adapter = new AppArrayAdapter(getContext(),
-            R.layout.app_preference_item, getEntries(), mEntryDrawables, selectedIndex);
-        builder.setAdapter(adapter, this);
+        builder.setAdapter(createListAdapter(), this);
         super.onPrepareDialogBuilder(builder);
     }
 
     @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
-        return new SavedState(getEntryValues(), getValue(), superState);
+        return new SavedState(getEntryValues(), getValue(), mShowItemNone, superState);
     }
 
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
         if (state instanceof SavedState) {
             SavedState savedState = (SavedState) state;
+            mShowItemNone = savedState.showItemNone;
             setPackageNames(savedState.entryValues, savedState.value);
             super.onRestoreInstanceState(savedState.superState);
         } else {
@@ -147,11 +168,14 @@
 
         public final CharSequence[] entryValues;
         public final CharSequence value;
+        public final boolean showItemNone;
         public final Parcelable superState;
 
-        public SavedState(CharSequence[] entryValues, CharSequence value, Parcelable superState) {
+        public SavedState(CharSequence[] entryValues, CharSequence value, boolean showItemNone,
+                Parcelable superState) {
             this.entryValues = entryValues;
             this.value = value;
+            this.showItemNone = showItemNone;
             this.superState = superState;
         }
 
@@ -164,6 +188,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeCharSequenceArray(entryValues);
             dest.writeCharSequence(value);
+            dest.writeInt(showItemNone ? 1 : 0);
             dest.writeParcelable(superState, flags);
         }
 
@@ -172,8 +197,9 @@
             public SavedState createFromParcel(Parcel source) {
                 CharSequence[] entryValues = source.readCharSequenceArray();
                 CharSequence value = source.readCharSequence();
+                boolean showItemNone = source.readInt() != 0;
                 Parcelable superState = source.readParcelable(getClass().getClassLoader());
-                return new SavedState(entryValues, value, superState);
+                return new SavedState(entryValues, value, showItemNone, superState);
             }
 
             @Override
diff --git a/src/com/android/settings/AppListPreferenceWithSettings.java b/src/com/android/settings/AppListPreferenceWithSettings.java
new file mode 100644
index 0000000..bbbd332
--- /dev/null
+++ b/src/com/android/settings/AppListPreferenceWithSettings.java
@@ -0,0 +1,59 @@
+package com.android.settings;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An AppListPreference with optional settings button.
+ */
+public class AppListPreferenceWithSettings extends AppListPreference {
+
+    private View mSettingsIcon;
+    private ComponentName mSettingsComponent;
+
+    public AppListPreferenceWithSettings(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setWidgetLayoutResource(R.layout.preference_widget_settings);
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        mSettingsIcon = view.findViewById(R.id.settings_button);
+        mSettingsIcon.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_MAIN);
+                intent.setComponent(mSettingsComponent);
+                getContext().startActivity(new Intent(intent));
+            }
+        });
+
+        ViewGroup container = (ViewGroup) mSettingsIcon.getParent();
+        container.setPaddingRelative(0, 0, 0, 0);
+
+        updateSettingsVisibility();
+    }
+
+    private void updateSettingsVisibility() {
+        if (mSettingsIcon == null) {
+            return;
+        }
+
+        if (mSettingsComponent == null) {
+            mSettingsIcon.setVisibility(View.GONE);
+        } else {
+            mSettingsIcon.setVisibility(View.VISIBLE);
+        }
+    }
+
+    protected void setSettingsComponent(ComponentName settings) {
+        mSettingsComponent = settings;
+        updateSettingsVisibility();
+    }
+}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 5a6a2f0..5fb94f0 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -41,7 +41,6 @@
     public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ }
     public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
     public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
-    public static class VoiceInputSettingsActivity extends SettingsActivity { /* empty */ }
     public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
     public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
     public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
@@ -50,6 +49,7 @@
     public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
+    public static class ManageAssistActivity extends SettingsActivity { /* empty */ }
     public static class AllApplicationsActivity extends SettingsActivity { /* empty */ }
     public static class HighPowerApplicationsActivity extends SettingsActivity { /* empty */ }
     public static class AppOpsSummaryActivity extends SettingsActivity {
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 3cef9ce..89231df 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -72,6 +72,7 @@
 import com.android.settings.accounts.AccountSyncSettings;
 import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.applications.ManageApplications;
+import com.android.settings.applications.ManageAssist;
 import com.android.settings.applications.ProcessStatsUi;
 import com.android.settings.applications.UsageAccessDetails;
 import com.android.settings.bluetooth.BluetoothSettings;
@@ -112,7 +113,6 @@
 import com.android.settings.sim.SimSettings;
 import com.android.settings.tts.TextToSpeechSettings;
 import com.android.settings.users.UserSettings;
-import com.android.settings.voice.VoiceInputSettings;
 import com.android.settings.vpn2.VpnSettings;
 import com.android.settings.wfd.WifiDisplaySettings;
 import com.android.settings.widget.SwitchBar;
@@ -290,7 +290,6 @@
             DateTimeSettings.class.getName(),
             LocalePicker.class.getName(),
             InputMethodAndLanguageSettings.class.getName(),
-            VoiceInputSettings.class.getName(),
             SpellCheckersSettings.class.getName(),
             UserDictionaryList.class.getName(),
             UserDictionarySettings.class.getName(),
@@ -298,6 +297,7 @@
             DisplaySettings.class.getName(),
             DeviceInfoSettings.class.getName(),
             ManageApplications.class.getName(),
+            ManageAssist.class.getName(),
             ProcessStatsUi.class.getName(),
             NotificationStation.class.getName(),
             LocationSettings.class.getName(),
diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java
index e052f8e..a264d50 100644
--- a/src/com/android/settings/VoiceInputOutputSettings.java
+++ b/src/com/android/settings/VoiceInputOutputSettings.java
@@ -31,7 +31,6 @@
     private static final String TAG = "VoiceInputOutputSettings";
 
     private static final String KEY_VOICE_CATEGORY = "voice_category";
-    private static final String KEY_VOICE_INPUT_SETTINGS = "voice_input_settings";
     private static final String KEY_TTS_SETTINGS = "tts_settings";
 
     private PreferenceGroup mParent;
@@ -47,19 +46,16 @@
     }
 
     public void onCreate() {
-
         mParent = mFragment.getPreferenceScreen();
         mVoiceCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_CATEGORY);
-        mVoiceInputSettingsPref = mVoiceCategory.findPreference(KEY_VOICE_INPUT_SETTINGS);
         mTtsSettingsPref = mVoiceCategory.findPreference(KEY_TTS_SETTINGS);
 
         populateOrRemovePreferences();
     }
 
     private void populateOrRemovePreferences() {
-        boolean hasVoiceInputPrefs = populateOrRemoveVoiceInputPrefs();
         boolean hasTtsPrefs = populateOrRemoveTtsPrefs();
-        if (!hasVoiceInputPrefs && !hasTtsPrefs) {
+        if (!hasTtsPrefs) {
             // There were no TTS settings and no recognizer settings,
             // so it should be safe to hide the preference category
             // entirely.
@@ -67,16 +63,6 @@
         }
     }
 
-    private boolean populateOrRemoveVoiceInputPrefs() {
-        VoiceInputHelper helper = new VoiceInputHelper(mFragment.getActivity());
-        if (!helper.hasItems()) {
-            mVoiceCategory.removePreference(mVoiceInputSettingsPref);
-            return false;
-        }
-
-        return true;
-    }
-
     private boolean populateOrRemoveTtsPrefs() {
         if (mTtsEngines.getEngines().isEmpty()) {
             mVoiceCategory.removePreference(mTtsSettingsPref);
diff --git a/src/com/android/settings/applications/DefaultAssistPreference.java b/src/com/android/settings/applications/DefaultAssistPreference.java
new file mode 100644
index 0000000..260d4b9
--- /dev/null
+++ b/src/com/android/settings/applications/DefaultAssistPreference.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2015 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.applications;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.settings.AppListPreferenceWithSettings;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultAssistPreference extends AppListPreferenceWithSettings {
+
+    private static final String TAG = DefaultAssistPreference.class.getSimpleName();
+
+    private final List<Info> mAvailableAssistants = new ArrayList<>();
+
+    public DefaultAssistPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setShowItemNone(true);
+        setDialogTitle(R.string.choose_assist_title);
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        final Info info = findAssistantByPackageName(value);
+        if (info == null) {
+            setAssistNone();
+            return true;
+        }
+
+        if (info.isVoiceInteractionService()) {
+            setAssistService(info);
+        } else {
+            setAssistActivity(info);
+        }
+        return true;
+    }
+
+    private void setAssistNone() {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, ITEM_NONE_VALUE);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
+
+        setSummary(getContext().getText(R.string.default_assist_none));
+        setSettingsComponent(null);
+    }
+
+    private void setAssistService(Info serviceInfo) {
+        final String serviceComponentName = serviceInfo.component.flattenToShortString();
+        final String serviceRecognizerName = new ComponentName(
+                serviceInfo.component.getPackageName(),
+                serviceInfo.voiceInteractionServiceInfo.getRecognitionService())
+                .flattenToShortString();
+
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, serviceComponentName);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
+
+        setSummary(getEntry());
+        final String settingsActivity =
+                serviceInfo.voiceInteractionServiceInfo.getSettingsActivity();
+        setSettingsComponent(settingsActivity == null ?
+                null :
+                new ComponentName(serviceInfo.component.getPackageName(), settingsActivity));
+    }
+
+    private void setAssistActivity(Info activityInfo) {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT, activityInfo.component.flattenToShortString());
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer());
+
+        setSummary(getEntry());
+        setSettingsComponent(null);
+    }
+
+    private String getDefaultRecognizer() {
+        ResolveInfo resolveInfo = getContext().getPackageManager().resolveService(
+                new Intent(RecognitionService.SERVICE_INTERFACE),
+                PackageManager.GET_META_DATA);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            Log.w(TAG, "Unable to resolve default voice recognition service.");
+            return "";
+        }
+
+        return new ComponentName(resolveInfo.serviceInfo.packageName,
+                resolveInfo.serviceInfo.name).flattenToShortString();
+    }
+
+    private Info findAssistantByPackageName(String packageName) {
+        for (int i = 0; i < mAvailableAssistants.size(); ++i) {
+            Info info = mAvailableAssistants.get(i);
+            if (info.component.getPackageName().equals(packageName)) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    private void addAssistServices() {
+        PackageManager pm = getContext().getPackageManager();
+
+        List<ResolveInfo> services = pm.queryIntentServices(
+                new Intent(VoiceInteractionService.SERVICE_INTERFACE),
+                PackageManager.GET_META_DATA);
+        for (int i = 0; i < services.size(); ++i) {
+            ResolveInfo resolveInfo = services.get(i);
+            VoiceInteractionServiceInfo voiceInteractionServiceInfo =
+                    new VoiceInteractionServiceInfo(pm, resolveInfo.serviceInfo);
+            if (!voiceInteractionServiceInfo.getSupportsAssist()) {
+                continue;
+            }
+
+            mAvailableAssistants.add(new Info(
+                    new ComponentName(resolveInfo.serviceInfo.packageName,
+                                      resolveInfo.serviceInfo.name),
+                    voiceInteractionServiceInfo));
+        }
+    }
+
+    private void addAssistActivities() {
+        PackageManager pm = getContext().getPackageManager();
+
+        List<ResolveInfo> activities = pm.queryIntentActivities(
+                new Intent(Intent.ACTION_ASSIST),
+                PackageManager.MATCH_DEFAULT_ONLY);
+        for (int i = 0; i < activities.size(); ++i) {
+            ResolveInfo resolveInfo = activities.get(i);
+            mAvailableAssistants.add(new Info(
+                    new ComponentName(resolveInfo.activityInfo.packageName,
+                                      resolveInfo.activityInfo.name)));
+        }
+    }
+
+    public ComponentName getCurrentAssist() {
+        String currentSetting = Settings.Secure.getString(getContext().getContentResolver(),
+                Settings.Secure.ASSISTANT);
+        return currentSetting == null ? null : ComponentName.unflattenFromString(currentSetting);
+    }
+
+    public void refreshAssistApps() {
+        mAvailableAssistants.clear();
+        addAssistServices();
+        addAssistActivities();
+
+        List<String> packages = new ArrayList<>();
+        for (int i = 0; i < mAvailableAssistants.size(); ++i) {
+            String packageName = mAvailableAssistants.get(i).component.getPackageName();
+            if (packages.contains(packageName)) {
+                // A service appears before an activity thus overrides it if from the same package.
+                continue;
+            }
+            packages.add(packageName);
+        }
+
+        ComponentName currentAssist = getCurrentAssist();
+        setPackageNames(packages.toArray(new String[packages.size()]),
+                currentAssist == null ? null : currentAssist.getPackageName());
+    }
+
+    private static class Info {
+        public final ComponentName component;
+        public final VoiceInteractionServiceInfo voiceInteractionServiceInfo;
+
+        Info(ComponentName component) {
+            this.component = component;
+            this.voiceInteractionServiceInfo = null;
+        }
+
+        Info(ComponentName component, VoiceInteractionServiceInfo voiceInteractionServiceInfo) {
+            this.component = component;
+            this.voiceInteractionServiceInfo = voiceInteractionServiceInfo;
+        }
+
+        public boolean isVoiceInteractionService() {
+            return voiceInteractionServiceInfo != null;
+        }
+    }
+}
diff --git a/src/com/android/settings/applications/ManageAssist.java b/src/com/android/settings/applications/ManageAssist.java
index d3bc0c8..f937811 100644
--- a/src/com/android/settings/applications/ManageAssist.java
+++ b/src/com/android/settings/applications/ManageAssist.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.applications;
 
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.SwitchPreference;
@@ -25,6 +28,7 @@
 import com.android.settings.InstrumentedFragment;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.voice.VoiceInputListPreference;
 
 /**
  * Settings screen to manage everything about assist.
@@ -32,18 +36,29 @@
 public class ManageAssist extends SettingsPreferenceFragment
         implements Preference.OnPreferenceChangeListener {
 
+    private static final String KEY_DEFAULT_ASSIST = "default_assist";
     private static final String KEY_CONTEXT = "context";
+    private static final String KEY_VOICE_INPUT = "voice_input_settings";
 
+    private DefaultAssistPreference mDefaultAssitPref;
     private SwitchPreference mContextPref;
+    private VoiceInputListPreference mVoiceInputPref;
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         addPreferencesFromResource(R.xml.manage_assist);
+
+        mDefaultAssitPref = (DefaultAssistPreference) findPreference(KEY_DEFAULT_ASSIST);
+        mDefaultAssitPref.setOnPreferenceChangeListener(this);
+
         mContextPref = (SwitchPreference) findPreference(KEY_CONTEXT);
         mContextPref.setChecked(Settings.Secure.getInt(getContentResolver(),
                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1) != 0);
         mContextPref.setOnPreferenceChangeListener(this);
+
+        mVoiceInputPref = (VoiceInputListPreference) findPreference(KEY_VOICE_INPUT);
+        updateUi();
     }
 
     @Override
@@ -58,6 +73,64 @@
                     (boolean) newValue ? 1 : 0);
             return true;
         }
+        if (preference == mDefaultAssitPref) {
+            String newAssitPackage = (String)newValue;
+            if (newAssitPackage == null ||
+                    newAssitPackage.contentEquals(DefaultAssistPreference.ITEM_NONE_VALUE)) {
+                setDefaultAssist(DefaultAssistPreference.ITEM_NONE_VALUE);
+                return false;
+            }
+
+            final String currentPackage = mDefaultAssitPref.getValue();
+            if (currentPackage == null || !newAssitPackage.contentEquals(currentPackage)) {
+                confirmNewAssist(newAssitPackage);
+            }
+            return false;
+        }
         return false;
     }
+
+    private void updateUi() {
+        mDefaultAssitPref.refreshAssistApps();
+
+        final ComponentName currentAssist = mDefaultAssitPref.getCurrentAssist();
+        final boolean hasAssistant = currentAssist != null;
+        if (hasAssistant) {
+            getPreferenceScreen().addPreference(mContextPref);
+        } else {
+            getPreferenceScreen().removePreference(mContextPref);
+        }
+
+        mVoiceInputPref.setAssistRestrict(currentAssist);
+        mVoiceInputPref.refreshVoiceInputs();
+    }
+
+    private void confirmNewAssist(final String newAssitPackage) {
+        final int selected = mDefaultAssitPref.findIndexOfValue(newAssitPackage);
+        final CharSequence appLabel = mDefaultAssitPref.getEntries()[selected];
+
+        final String title = getString(R.string.assistant_security_warning_title, appLabel);
+        final String message = getString(R.string.assistant_security_warning, appLabel);
+
+        final DialogInterface.OnClickListener onAgree = new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                setDefaultAssist(newAssitPackage);
+            }
+        };
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+        builder.setTitle(title)
+                .setMessage(message)
+                .setCancelable(true)
+                .setPositiveButton(R.string.assistant_security_warning_agree, onAgree)
+                .setNegativeButton(R.string.assistant_security_warning_disagree, null);
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
+    private void setDefaultAssist(String assistPackage) {
+        mDefaultAssitPref.setValue(assistPackage);
+        updateUi();
+    }
 }
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index 8f23ce9..93ece66 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -790,14 +790,6 @@
                 indexables.add(indexable);
             }
 
-            // Voice input
-            indexable = new SearchIndexableRaw(context);
-            indexable.key = "voice_input_settings";
-            indexable.title = context.getString(R.string.voice_input_settings);
-            indexable.screenTitle = screenTitle;
-            indexable.keywords = context.getString(R.string.keywords_voice_input);
-            indexables.add(indexable);
-
             // Text-to-speech.
             TtsEngines ttsEngines = new TtsEngines(context);
             if (!ttsEngines.getEngines().isEmpty()) {
diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java
index 6481aba..3edfee7 100644
--- a/src/com/android/settings/search/Ranking.java
+++ b/src/com/android/settings/search/Ranking.java
@@ -49,7 +49,6 @@
 import com.android.settings.print.PrintSettingsFragment;
 import com.android.settings.sim.SimSettings;
 import com.android.settings.users.UserSettings;
-import com.android.settings.voice.VoiceInputSettings;
 import com.android.settings.wifi.AdvancedWifiSettings;
 import com.android.settings.wifi.SavedAccessPointsWifiSettings;
 import com.android.settings.wifi.WifiSettings;
@@ -154,7 +153,6 @@
 
         // IMEs
         sRankMap.put(InputMethodAndLanguageSettings.class.getName(), RANK_IME);
-        sRankMap.put(VoiceInputSettings.class.getName(), RANK_IME);
 
         // Privacy
         sRankMap.put(PrivacySettings.class.getName(), RANK_PRIVACY);
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 7cb0dd1..f3c0b42 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -50,7 +50,6 @@
 import com.android.settings.print.PrintSettingsFragment;
 import com.android.settings.sim.SimSettings;
 import com.android.settings.users.UserSettings;
-import com.android.settings.voice.VoiceInputSettings;
 import com.android.settings.wifi.AdvancedWifiSettings;
 import com.android.settings.wifi.SavedAccessPointsWifiSettings;
 import com.android.settings.wifi.WifiSettings;
@@ -248,13 +247,6 @@
                         InputMethodAndLanguageSettings.class.getName(),
                         R.drawable.ic_settings_language));
 
-        sResMap.put(VoiceInputSettings.class.getName(),
-                new SearchIndexableResource(
-                        Ranking.getRankForClassName(VoiceInputSettings.class.getName()),
-                        NO_DATA_RES_ID,
-                        VoiceInputSettings.class.getName(),
-                        R.drawable.ic_settings_language));
-
         sResMap.put(PrivacySettings.class.getName(),
                 new SearchIndexableResource(
                         Ranking.getRankForClassName(PrivacySettings.class.getName()),
diff --git a/src/com/android/settings/voice/VoiceInputListPreference.java b/src/com/android/settings/voice/VoiceInputListPreference.java
new file mode 100644
index 0000000..a131d21
--- /dev/null
+++ b/src/com/android/settings/voice/VoiceInputListPreference.java
@@ -0,0 +1,148 @@
+package com.android.settings.voice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+
+import com.android.settings.AppListPreferenceWithSettings;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VoiceInputListPreference extends AppListPreferenceWithSettings {
+
+    private VoiceInputHelper mHelper;
+
+    // The assist component name to restrict available voice inputs.
+    private ComponentName mAssistRestrict;
+
+    private final List<Integer> mAvailableIndexes = new ArrayList<>();
+
+    public VoiceInputListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setDialogTitle(R.string.choose_voice_input_title);
+    }
+
+    @Override
+    protected ListAdapter createListAdapter() {
+        return new CustomAdapter(getContext(), getEntries());
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        for (int i = 0; i < mHelper.mAvailableInteractionInfos.size(); ++i) {
+            VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
+            if (info.key.equals(value)) {
+                Settings.Secure.putString(getContext().getContentResolver(),
+                        Settings.Secure.VOICE_INTERACTION_SERVICE, value);
+                Settings.Secure.putString(getContext().getContentResolver(),
+                        Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                        new ComponentName(info.service.packageName,
+                                info.serviceInfo.getRecognitionService())
+                                .flattenToShortString());
+                setSummary(getEntry());
+                setSettingsComponent(info.settings);
+                return true;
+            }
+        }
+
+        for (int i = 0; i < mHelper.mAvailableRecognizerInfos.size(); ++i) {
+            VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
+            if (info.key.equals(value)) {
+                Settings.Secure.putString(getContext().getContentResolver(),
+                        Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+                Settings.Secure.putString(getContext().getContentResolver(),
+                        Settings.Secure.VOICE_RECOGNITION_SERVICE, value);
+                setSummary(getEntry());
+                setSettingsComponent(info.settings);
+               return true;
+            }
+        }
+
+        setSettingsComponent(null);
+        return true;
+    }
+
+    @Override
+    public void setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName) {
+        // Skip since all entries are created from |mHelper|.
+    }
+
+    public void setAssistRestrict(ComponentName assistRestrict) {
+        mAssistRestrict = assistRestrict;
+    }
+
+    public void refreshVoiceInputs() {
+        mHelper = new VoiceInputHelper(getContext());
+        mHelper.buildUi();
+
+        final String assistKey =
+                mAssistRestrict == null ? "" : mAssistRestrict.flattenToShortString();
+
+        mAvailableIndexes.clear();
+        List<CharSequence> entries = new ArrayList<>();
+        List<CharSequence> values = new ArrayList<>();
+        for (int i = 0; i < mHelper.mAvailableInteractionInfos.size(); ++i) {
+            VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
+            entries.add(info.appLabel);
+            values.add(info.key);
+
+            if (info.key.contentEquals(assistKey)) {
+                mAvailableIndexes.add(i);
+            }
+        }
+
+        final boolean assitIsService = !mAvailableIndexes.isEmpty();
+        final int serviceCount = entries.size();
+
+        for (int i = 0; i < mHelper.mAvailableRecognizerInfos.size(); ++i) {
+            VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
+            entries.add(info.label);
+            values.add(info.key);
+            if (!assitIsService) {
+                mAvailableIndexes.add(serviceCount + i);
+            }
+        }
+        setEntries(entries.toArray(new CharSequence[entries.size()]));
+        setEntryValues(values.toArray(new CharSequence[values.size()]));
+
+        if (mHelper.mCurrentVoiceInteraction != null) {
+            setValue(mHelper.mCurrentVoiceInteraction.flattenToShortString());
+        } else if (mHelper.mCurrentRecognizer != null) {
+            setValue(mHelper.mCurrentRecognizer.flattenToShortString());
+        } else {
+            setValue(null);
+        }
+    }
+
+    private class CustomAdapter extends ArrayAdapter<CharSequence> {
+
+        public CustomAdapter(Context context, CharSequence[] objects) {
+            super(context, com.android.internal.R.layout.select_dialog_singlechoice_material,
+                    android.R.id.text1, objects);
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return mAvailableIndexes.contains(position);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = super.getView(position, convertView, parent);
+            view.setEnabled(isEnabled(position));
+            return view;
+        }
+    }
+}
diff --git a/src/com/android/settings/voice/VoiceInputPreference.java b/src/com/android/settings/voice/VoiceInputPreference.java
deleted file mode 100644
index 38ef0ca..0000000
--- a/src/com/android/settings/voice/VoiceInputPreference.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2014 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.voice;
-
-import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.preference.Preference;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Checkable;
-import android.widget.CompoundButton;
-import android.widget.RadioButton;
-
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-
-public final class VoiceInputPreference extends Preference {
-
-    private static final String TAG = "VoiceInputPreference";
-
-    private final CharSequence mLabel;
-
-    private final CharSequence mAppLabel;
-
-    private final CharSequence mAlertText;
-
-    private final ComponentName mSettingsComponent;
-
-    /**
-     * The shared radio button state, which button is checked etc.
-     */
-    private final RadioButtonGroupState mSharedState;
-
-    /**
-     * When true, the change callbacks on the radio button will not
-     * fire.
-     */
-    private volatile boolean mPreventRadioButtonCallbacks;
-
-    private View mSettingsIcon;
-    private RadioButton mRadioButton;
-
-    private final CompoundButton.OnCheckedChangeListener mRadioChangeListener =
-        new CompoundButton.OnCheckedChangeListener() {
-            @Override
-            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-                onRadioButtonClicked(buttonView, isChecked);
-            }
-        };
-
-    public VoiceInputPreference(Context context, VoiceInputHelper.BaseInfo info,
-            CharSequence summary, CharSequence alertText, RadioButtonGroupState state) {
-        super(context);
-        setLayoutResource(R.layout.preference_tts_engine);
-
-        mSharedState = state;
-        mLabel = info.label;
-        mAppLabel = info.appLabel;
-        mAlertText = alertText;
-        mSettingsComponent = info.settings;
-        mPreventRadioButtonCallbacks = false;
-
-        setKey(info.key);
-        setTitle(info.label);
-        setSummary(summary);
-    }
-
-    @Override
-    public View getView(View convertView, ViewGroup parent) {
-        if (mSharedState == null) {
-            throw new IllegalStateException("Call to getView() before a call to" +
-                    "setSharedState()");
-        }
-
-        View view = super.getView(convertView, parent);
-        final RadioButton rb = (RadioButton) view.findViewById(R.id.tts_engine_radiobutton);
-        rb.setOnCheckedChangeListener(mRadioChangeListener);
-
-        boolean isChecked = getKey().equals(mSharedState.getCurrentKey());
-        if (isChecked) {
-            mSharedState.setCurrentChecked(rb);
-        }
-
-        mPreventRadioButtonCallbacks = true;
-        rb.setChecked(isChecked);
-        mPreventRadioButtonCallbacks = false;
-
-        mRadioButton = rb;
-
-        View textLayout = view.findViewById(R.id.tts_engine_pref_text);
-        textLayout.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (!rb.isChecked()) {
-                    onRadioButtonClicked(rb, true);
-                }
-            }
-        });
-
-        mSettingsIcon = view.findViewById(R.id.tts_engine_settings);
-        mSettingsIcon.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(Intent.ACTION_MAIN);
-                intent.setComponent(mSettingsComponent);
-                getContext().startActivity(new Intent(intent));
-            }
-        });
-        updateCheckedState(isChecked);
-
-        return view;
-    }
-
-    private boolean shouldDisplayAlert() {
-        return mAlertText != null;
-    }
-
-    private void displayAlert(
-            final DialogInterface.OnClickListener positiveOnClickListener,
-            final DialogInterface.OnClickListener negativeOnClickListener) {
-        Log.i(TAG, "Displaying data alert for :" + getKey());
-
-        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-        String msg = String.format(getContext().getResources().getConfiguration().locale,
-                mAlertText.toString(), mAppLabel);
-        builder.setTitle(android.R.string.dialog_alert_title)
-                .setMessage(msg)
-                .setCancelable(true)
-                .setPositiveButton(android.R.string.ok, positiveOnClickListener)
-                .setNegativeButton(android.R.string.cancel, negativeOnClickListener)
-                .setOnCancelListener(new DialogInterface.OnCancelListener() {
-                    @Override public void onCancel(DialogInterface dialog) {
-                        negativeOnClickListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
-                    }
-                });
-
-        AlertDialog dialog = builder.create();
-        dialog.show();
-    }
-
-    public void doClick() {
-        mRadioButton.performClick();
-    }
-
-    void updateCheckedState(boolean isChecked) {
-        if (mSettingsComponent != null) {
-            mSettingsIcon.setVisibility(View.VISIBLE);
-            if (isChecked) {
-                mSettingsIcon.setEnabled(true);
-                mSettingsIcon.setAlpha(1);
-            } else {
-                mSettingsIcon.setEnabled(false);
-                mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
-            }
-        } else {
-            mSettingsIcon.setVisibility(View.GONE);
-        }
-    }
-
-    void onRadioButtonClicked(final CompoundButton buttonView, boolean isChecked) {
-        if (mPreventRadioButtonCallbacks) {
-            return;
-        }
-        if (mSharedState.getCurrentChecked() == buttonView) {
-            updateCheckedState(isChecked);
-            return;
-        }
-
-        if (isChecked) {
-            // Should we alert user? if that's true, delay making engine current one.
-            if (shouldDisplayAlert()) {
-                displayAlert(new DialogInterface.OnClickListener() {
-                                 @Override
-                                 public void onClick(DialogInterface dialog, int which) {
-                                     makeCurrentChecked(buttonView);
-                                 }
-                             }, new DialogInterface.OnClickListener() {
-                                 @Override
-                                 public void onClick(DialogInterface dialog, int which) {
-                                     // Undo the click.
-                                     buttonView.setChecked(false);
-                                 }
-                             }
-                );
-            } else {
-                // Privileged engine, set it current
-                makeCurrentChecked(buttonView);
-            }
-        } else {
-            updateCheckedState(isChecked);
-        }
-    }
-
-    void makeCurrentChecked(Checkable current) {
-        if (mSharedState.getCurrentChecked() != null) {
-            mSharedState.getCurrentChecked().setChecked(false);
-        }
-        mSharedState.setCurrentChecked(current);
-        mSharedState.setCurrentKey(getKey());
-        updateCheckedState(true);
-        callChangeListener(mSharedState.getCurrentKey());
-        current.setChecked(true);
-    }
-
-    /**
-     * Holds all state that is common to this group of radio buttons, such
-     * as the currently selected key and the currently checked compound button.
-     * (which corresponds to this key).
-     */
-    public interface RadioButtonGroupState {
-        String getCurrentKey();
-        Checkable getCurrentChecked();
-
-        void setCurrentKey(String key);
-        void setCurrentChecked(Checkable current);
-    }
-}
diff --git a/src/com/android/settings/voice/VoiceInputSettings.java b/src/com/android/settings/voice/VoiceInputSettings.java
deleted file mode 100644
index bac297e..0000000
--- a/src/com/android/settings/voice/VoiceInputSettings.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2014 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.voice;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.preference.Preference;
-import android.provider.Settings;
-import android.service.voice.VoiceInteractionService;
-import android.service.voice.VoiceInteractionServiceInfo;
-import android.speech.RecognitionService;
-import com.android.internal.logging.MetricsLogger;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable;
-import com.android.settings.search.SearchIndexableRaw;
-import com.android.settings.voice.VoiceInputPreference.RadioButtonGroupState;
-
-import android.os.Bundle;
-import android.preference.PreferenceCategory;
-import android.widget.Checkable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class VoiceInputSettings extends SettingsPreferenceFragment implements
-        Preference.OnPreferenceClickListener, RadioButtonGroupState, Indexable {
-
-    private static final String TAG = "VoiceInputSettings";
-    private static final boolean DBG = false;
-
-    /**
-     * Preference key for the engine selection preference.
-     */
-    private static final String KEY_SERVICE_PREFERENCE_SECTION =
-            "voice_service_preference_section";
-
-    private PreferenceCategory mServicePreferenceCategory;
-
-    private CharSequence mInteractorSummary;
-    private CharSequence mRecognizerSummary;
-    private CharSequence mInteractorWarning;
-
-    /**
-     * The currently selected engine.
-     */
-    private String mCurrentKey;
-
-    /**
-     * The engine checkbox that is currently checked. Saves us a bit of effort
-     * in deducing the right one from the currently selected engine.
-     */
-    private Checkable mCurrentChecked;
-
-    private VoiceInputHelper mHelper;
-
-    @Override
-    protected int getMetricsCategory() {
-        return MetricsLogger.VOICE_INPUT;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        addPreferencesFromResource(R.xml.voice_input_settings);
-
-        mServicePreferenceCategory = (PreferenceCategory) findPreference(
-                KEY_SERVICE_PREFERENCE_SECTION);
-
-        mInteractorSummary = getActivity().getText(
-                R.string.voice_interactor_preference_summary);
-        mRecognizerSummary = getActivity().getText(
-                R.string.voice_recognizer_preference_summary);
-        mInteractorWarning = getActivity().getText(R.string.voice_interaction_security_warning);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        initSettings();
-    }
-
-    private void initSettings() {
-        mHelper = new VoiceInputHelper(getActivity());
-        mHelper.buildUi();
-
-        mServicePreferenceCategory.removeAll();
-
-        if (mHelper.mCurrentVoiceInteraction != null) {
-            mCurrentKey = mHelper.mCurrentVoiceInteraction.flattenToShortString();
-        } else if (mHelper.mCurrentRecognizer != null) {
-            mCurrentKey = mHelper.mCurrentRecognizer.flattenToShortString();
-        } else {
-            mCurrentKey = null;
-        }
-
-        for (int i=0; i<mHelper.mAvailableInteractionInfos.size(); i++) {
-            VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
-            VoiceInputPreference pref = new VoiceInputPreference(getActivity(), info,
-                    mInteractorSummary, mInteractorWarning, this);
-            mServicePreferenceCategory.addPreference(pref);
-        }
-
-        for (int i=0; i<mHelper.mAvailableRecognizerInfos.size(); i++) {
-            VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
-            VoiceInputPreference pref = new VoiceInputPreference(getActivity(), info,
-                    mRecognizerSummary, null, this);
-            mServicePreferenceCategory.addPreference(pref);
-        }
-    }
-
-    @Override
-    public Checkable getCurrentChecked() {
-        return mCurrentChecked;
-    }
-
-    @Override
-    public String getCurrentKey() {
-        return mCurrentKey;
-    }
-
-    @Override
-    public void setCurrentChecked(Checkable current) {
-        mCurrentChecked = current;
-    }
-
-    @Override
-    public void setCurrentKey(String key) {
-        mCurrentKey = key;
-        for (int i=0; i<mHelper.mAvailableInteractionInfos.size(); i++) {
-            VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
-            if (info.key.equals(key)) {
-                // Put the new value back into secure settings.
-                Settings.Secure.putString(getActivity().getContentResolver(),
-                        Settings.Secure.VOICE_INTERACTION_SERVICE, key);
-                Settings.Secure.putString(getActivity().getContentResolver(),
-                        Settings.Secure.VOICE_RECOGNITION_SERVICE,
-                        new ComponentName(info.service.packageName,
-                                info.serviceInfo.getRecognitionService())
-                                .flattenToShortString());
-                return;
-            }
-        }
-
-        for (int i=0; i<mHelper.mAvailableRecognizerInfos.size(); i++) {
-            VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
-            if (info.key.equals(key)) {
-                Settings.Secure.putString(getActivity().getContentResolver(),
-                        Settings.Secure.VOICE_INTERACTION_SERVICE, "");
-                Settings.Secure.putString(getActivity().getContentResolver(),
-                        Settings.Secure.VOICE_RECOGNITION_SERVICE, key);
-                return;
-            }
-        }
-    }
-
-    @Override
-    public boolean onPreferenceClick(Preference preference) {
-        if (preference instanceof VoiceInputPreference) {
-            ((VoiceInputPreference)preference).doClick();
-        }
-        return true;
-    }
-
-    // For Search
-    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-        new BaseSearchIndexProvider() {
-
-            @Override
-            public List<SearchIndexableRaw> getRawDataToIndex(Context context,
-                    boolean enabled) {
-
-                List<SearchIndexableRaw> indexables = new ArrayList<>();
-
-                final String screenTitle = context.getString(R.string.voice_input_settings_title);
-
-                SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-                indexable.key = "voice_service_preference_section_title";
-                indexable.title = context.getString(R.string.voice_service_preference_section_title);
-                indexable.screenTitle = screenTitle;
-                indexables.add(indexable);
-
-                final List<ResolveInfo> voiceInteractions =
-                        context.getPackageManager().queryIntentServices(
-                                new Intent(VoiceInteractionService.SERVICE_INTERFACE),
-                                PackageManager.GET_META_DATA);
-
-                final int countInteractions = voiceInteractions.size();
-                for (int i = 0; i < countInteractions; i++) {
-                    ResolveInfo info = voiceInteractions.get(i);
-                    VoiceInteractionServiceInfo visInfo = new VoiceInteractionServiceInfo(
-                            context.getPackageManager(), info.serviceInfo);
-                    if (visInfo.getParseError() != null) {
-                        continue;
-                    }
-                    indexables.add(getSearchIndexableRaw(context, info, screenTitle));
-                }
-
-                final List<ResolveInfo> recognitions =
-                        context.getPackageManager().queryIntentServices(
-                                new Intent(RecognitionService.SERVICE_INTERFACE),
-                                PackageManager.GET_META_DATA);
-
-                final int countRecognitions = recognitions.size();
-                for (int i = 0; i < countRecognitions; i++) {
-                    ResolveInfo info = recognitions.get(i);
-                    indexables.add(getSearchIndexableRaw(context, info, screenTitle));
-                }
-
-                return indexables;
-            }
-
-            private SearchIndexableRaw getSearchIndexableRaw(Context context,
-                    ResolveInfo info, String screenTitle) {
-
-                ServiceInfo serviceInfo = info.serviceInfo;
-                ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                        serviceInfo.name);
-
-                SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-                indexable.key = componentName.flattenToString();
-                indexable.title = info.loadLabel(context.getPackageManager()).toString();
-                indexable.screenTitle = screenTitle;
-
-                return indexable;
-            }
-        };
-}