<string name="keyboard_settings_title">Android keyboard</string>
<!-- Title for the 'voice input' category of voice input/output settings -->
<string name="voice_category">Speech</string>
- <!-- Title for the voice interactor setting in voice input/output settings -->
- <string name="voice_interactor_title">Voice input</string>
- <!-- Title for the link to settings for the chosen voice interactor in voice input/output
- settings -->
- <string name="voice_interactor_settings_title">Voice input</string>
- <!-- Summary for the link to settings for the chosen voice interactor in voice input/output
- settings. Would say something like, e.g., "Settings for 'Google'". -->
- <string name="voice_interactor_settings_summary">Settings for
- \'<xliff:g id="interactor_name">%s</xliff:g>\'</string>
- <!-- Label to show for no voice interactor selector -->
- <string name="no_voice_interactor">None</string>
- <!-- Title for the voice recognizer setting in voice input/output settings -->
- <string name="recognizer_title">Voice recognizer</string>
- <!-- Title for the link to settings for the chosen voice recognizer in voice input/output settings -->
- <string name="recognizer_settings_title">Voice Search</string>
- <!-- Summary for the link to settings for the chosen voice recognizer in voice input/output settings.
- Would say something like, e.g., "Settings for 'Google'". -->
- <string name="recognizer_settings_summary">Settings for \'<xliff:g id="recognizer_name">%s</xliff:g>\'</string>
+
+ <!-- Voice input settings --><skip />
+ <!-- [CHAR_LIMIT=NONE] Name of the settings item to open the voice input settings. -->
+ <string name="voice_input_settings">Voice input settings</string>
+ <!-- [CHAR_LIMIT=NONE] Title of the screen of the voice input settings -->
+ <string name="voice_input_settings_title">Voice input</string>
+ <!-- [CHAR LIMIT=50] The text for the settings section in which users select
+ a voice interaction or recognition service to use. -->
+ <string name="voice_service_preference_section_title">Voice input services</string>
+ <!-- [CHAR LIMIT=NONE] The summary text for the voice service preference that is
+ a full voice interaction service. -->
+ <string name="voice_interactor_preference_summary">Full voice interaction</string>
+ <!-- [CHAR LIMIT=NONE] The summary text for the voice service preference that is
+ a simple voice recognition service. -->
+ <string name="voice_recognizer_preference_summary">Simple voice recognition</string>
+ <!-- [CHAR_LIMIT=NONE] Warning message about security implications of enabling a
+ voice interaction service, displayed as a dialog
+ message when the user selects to enable a service. -->
+ <string name="voice_interaction_security_warning">This voice input service will be able to
+ control all voice enabled applications on your behalf.
+ It comes from the <xliff:g id="voice_input_service_app_name">%s</xliff:g> application.
+ Enable the use of this service?</string>
<!-- Text-To-Speech (TTS) settings --><skip />
<!-- Name of the TTS package as listed by the package manager. -->
<string name="keywords_storage">space disk hard drive</string>
<string name="keywords_battery">power</string>
<string name="keywords_spell_checker">spelling</string>
+ <string name="keywords_voice_input">recognizer input speech speak language hands-free hand free recognition offensive word audio history bluetooth headset</string>
<string name="keywords_text_to_speech_output">rate language default speak speaking</string>
<string name="keywords_date_and_time">clock</string>
<string name="keywords_factory_data_reset">wipe delete</string>
<string name="keywords_accounts">account</string>
<string name="keywords_users">restriction restrict restricted</string>
<string name="keywords_keyboard_and_ime">text correction correct sound vibrate auto language gesture suggest suggestion theme offensive word type emoji</string>
- <string name="keywords_search_voice">language hands-free hand free recognition offensive word audio history bluetooth headset</string>
<!-- NFC Wi-Fi pairing/setup strings-->
android:key="voice_category"
android:title="@string/voice_category" >
- <!-- entries, entryValues, and defaultValue will be populated programmatically. -->
- <ListPreference
- android:key="voice_interactor"
- android:title="@string/voice_interactor_title"
- android:dialogTitle="@string/voice_interactor_title"
- />
-
- <!-- An intent for this preference will be populated programmatically. -->
- <PreferenceScreen
- android:key="voice_interactor_settings"
- android:title="@string/voice_interactor_settings_title"
- />
-
- <!-- entries, entryValues, and defaultValue will be populated programmatically. -->
- <ListPreference
- android:key="recognizer"
- android:title="@string/recognizer_title"
- android:dialogTitle="@string/recognizer_title"
- />
-
- <!-- An intent for this preference will be populated programmatically. -->
<PreferenceScreen
- android:key="recognizer_settings"
- android:title="@string/recognizer_settings_title"
- settings:keywords="@string/keywords_search_voice"
+ android:key="voice_input_settings"
+ android:title="@string/voice_input_settings_title"
+ settings:keywords="@string/keywords_voice_input"
+ android:fragment="com.android.settings.voice.VoiceInputSettings"
/>
<PreferenceScreen
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/voice_input_settings_title">
+
+ <!-- The contents of this category are filled in by the Java code
+ based on the list of available voice interaction and recognition services. -->
+ <PreferenceCategory android:key="voice_service_preference_section"
+ android:title="@string/voice_service_preference_section_title" />
+
+</PreferenceScreen>
package com.android.settings;
-import android.Manifest;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.provider.Settings;
-import android.service.voice.VoiceInteractionService;
-import android.speech.RecognitionService;
import android.speech.tts.TtsEngines;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+import com.android.settings.voice.VoiceInputHelper;
/**
* Settings screen for voice input/output.
*/
-public class VoiceInputOutputSettings implements OnPreferenceChangeListener {
+public class VoiceInputOutputSettings {
private static final String TAG = "VoiceInputOutputSettings";
private static final String KEY_VOICE_CATEGORY = "voice_category";
- private static final String KEY_VOICE_INTERACTOR = "voice_interactor";
- private static final String KEY_VOICE_INTERACTOR_SETTINGS = "voice_interactor_settings";
- private static final String KEY_RECOGNIZER = "recognizer";
- private static final String KEY_RECOGNIZER_SETTINGS = "recognizer_settings";
+ private static final String KEY_VOICE_INPUT_SETTINGS = "voice_input_settings";
private static final String KEY_TTS_SETTINGS = "tts_settings";
private PreferenceGroup mParent;
- private ListPreference mVoiceInteractionPref;
- private PreferenceScreen mVoiceInteractionSettingsPref;
private PreferenceCategory mVoiceCategory;
- private ListPreference mRecognizerPref;
- private PreferenceScreen mRecognizerSettingsPref;
+ private Preference mVoiceInputSettingsPref;
private Preference mTtsSettingsPref;
private final SettingsPreferenceFragment mFragment;
private final TtsEngines mTtsEngines;
- private HashMap<String, ResolveInfo> mAvailableVoiceInteractionsMap;
-
- private HashMap<String, ResolveInfo> mAvailableRecognizersMap;
-
public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) {
mFragment = fragment;
mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext());
mParent = mFragment.getPreferenceScreen();
mVoiceCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_CATEGORY);
- mVoiceInteractionPref = (ListPreference) mVoiceCategory.findPreference(
- KEY_VOICE_INTERACTOR);
- mVoiceInteractionPref.setOnPreferenceChangeListener(this);
- mVoiceInteractionSettingsPref = (PreferenceScreen)mVoiceCategory.findPreference(
- KEY_VOICE_INTERACTOR_SETTINGS);
- mRecognizerPref = (ListPreference) mVoiceCategory.findPreference(KEY_RECOGNIZER);
- mRecognizerSettingsPref = (PreferenceScreen)
- mVoiceCategory.findPreference(KEY_RECOGNIZER_SETTINGS);
- mRecognizerPref.setOnPreferenceChangeListener(this);
+ mVoiceInputSettingsPref = mVoiceCategory.findPreference(KEY_VOICE_INPUT_SETTINGS);
mTtsSettingsPref = mVoiceCategory.findPreference(KEY_TTS_SETTINGS);
- mAvailableVoiceInteractionsMap = new HashMap<String, ResolveInfo>();
- mAvailableRecognizersMap = new HashMap<String, ResolveInfo>();
-
populateOrRemovePreferences();
}
private void populateOrRemovePreferences() {
- boolean hasVoiceInteractionPrefs = populateOrRemoveVoiceInteractionPrefs();
- boolean hasRecognizerPrefs = populateOrRemoveRecognizerPrefs();
+ boolean hasVoiceInputPrefs = populateOrRemoveVoiceInputPrefs();
boolean hasTtsPrefs = populateOrRemoveTtsPrefs();
- if (!hasVoiceInteractionPrefs && !hasRecognizerPrefs && !hasTtsPrefs) {
+ if (!hasVoiceInputPrefs && !hasTtsPrefs) {
// There were no TTS settings and no recognizer settings,
// so it should be safe to hide the preference category
// entirely.
}
}
- private boolean populateOrRemoveVoiceInteractionPrefs() {
- List<ResolveInfo> availableVoiceServices =
- mFragment.getPackageManager().queryIntentServices(
- new Intent(VoiceInteractionService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
- for (int i=0; i<availableVoiceServices.size(); i++) {
- ResolveInfo ri = availableVoiceServices.get(i);
- if (!Manifest.permission.BIND_VOICE_INTERACTION.equals(ri.serviceInfo.permission)) {
- availableVoiceServices.remove(i);
- }
- }
- int numAvailable = availableVoiceServices.size();
-
- if (numAvailable == 0) {
- mVoiceCategory.removePreference(mVoiceInteractionPref);
- mVoiceCategory.removePreference(mVoiceInteractionSettingsPref);
+ private boolean populateOrRemoveVoiceInputPrefs() {
+ VoiceInputHelper helper = new VoiceInputHelper(mFragment.getActivity());
+ if (!helper.hasItems()) {
+ mVoiceCategory.removePreference(mVoiceInputSettingsPref);
return false;
}
- populateVoiceInteractionPreference(availableVoiceServices);
-
- // In this case, there was at least one available recognizer so
- // we populated the settings.
- return true;
- }
-
- private boolean populateOrRemoveRecognizerPrefs() {
- List<ResolveInfo> availableRecognitionServices =
- mFragment.getPackageManager().queryIntentServices(
- new Intent(RecognitionService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
- int numAvailable = availableRecognitionServices.size();
-
- if (numAvailable == 0) {
- mVoiceCategory.removePreference(mRecognizerPref);
- mVoiceCategory.removePreference(mRecognizerSettingsPref);
- return false;
- }
-
- if (numAvailable == 1) {
- // Only one recognizer available, so don't show the list of choices, but do
- // set up the link to settings for the available recognizer.
- mVoiceCategory.removePreference(mRecognizerPref);
-
- // But first set up the available recognizers map with just the one recognizer.
- ResolveInfo resolveInfo = availableRecognitionServices.get(0);
- String recognizerComponent =
- new ComponentName(resolveInfo.serviceInfo.packageName,
- resolveInfo.serviceInfo.name).flattenToShortString();
-
- mAvailableRecognizersMap.put(recognizerComponent, resolveInfo);
-
- String currentSetting = Settings.Secure.getString(
- mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
- updateRecognizerSettingsLink(currentSetting);
- } else {
- // Multiple recognizers available, so show the full list of choices.
- populateRecognizerPreference(availableRecognitionServices);
- }
-
- // In this case, there was at least one available recognizer so
- // we populated the settings.
return true;
}
return true;
}
-
- private void populateVoiceInteractionPreference(List<ResolveInfo> voiceInteractors) {
- int size = voiceInteractors.size();
- CharSequence[] entries = new CharSequence[size+1];
- CharSequence[] values = new CharSequence[size+1];
-
- // Get the current value from the secure setting.
- String currentSetting = Settings.Secure.getString(
- mFragment.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
-
- // Iterate through all the available recognizers and load up their info to show
- // in the preference. Also build up a map of recognizer component names to their
- // ResolveInfos - we'll need that a little later.
- for (int i = 0; i < size; i++) {
- ResolveInfo resolveInfo = voiceInteractors.get(i);
- String recognizerComponent =
- new ComponentName(resolveInfo.serviceInfo.packageName,
- resolveInfo.serviceInfo.name).flattenToShortString();
-
- mAvailableVoiceInteractionsMap.put(recognizerComponent, resolveInfo);
-
- entries[i] = resolveInfo.loadLabel(mFragment.getPackageManager());
- values[i] = recognizerComponent;
- }
-
- entries[size] = mFragment.getString(R.string.no_voice_interactor);
- values[size] = "";
-
- mVoiceInteractionPref.setEntries(entries);
- mVoiceInteractionPref.setEntryValues(values);
-
- mVoiceInteractionPref.setDefaultValue(currentSetting);
- mVoiceInteractionPref.setValue(currentSetting);
-
- updateVoiceInteractionSettingsLink(currentSetting);
- }
-
- private void updateVoiceInteractionSettingsLink(String currentSetting) {
- ResolveInfo currentRecognizer = mAvailableVoiceInteractionsMap.get(currentSetting);
- if (currentRecognizer == null) {
- mVoiceInteractionPref.setSummary(mFragment.getString(R.string.no_voice_interactor));
- mVoiceInteractionPref.setValue("");
- return;
- }
-
- ServiceInfo si = currentRecognizer.serviceInfo;
- XmlResourceParser parser = null;
- String settingsActivity = null;
- try {
- parser = si.loadXmlMetaData(mFragment.getPackageManager(),
- VoiceInteractionService.SERVICE_META_DATA);
- if (parser == null) {
- throw new XmlPullParserException("No " + VoiceInteractionService.SERVICE_META_DATA +
- " meta-data for " + si.packageName);
- }
-
- Resources res = mFragment.getPackageManager().getResourcesForApplication(
- si.applicationInfo);
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- }
-
- String nodeName = parser.getName();
- if (!"voice-interaction-service".equals(nodeName)) {
- throw new XmlPullParserException(
- "Meta-data does not start with voice-interaction-service tag");
- }
-
- TypedArray array = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.VoiceInteractionService);
- settingsActivity = array.getString(
- com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
- array.recycle();
- } catch (XmlPullParserException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (IOException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } finally {
- if (parser != null) parser.close();
- }
-
- mVoiceInteractionPref.setSummary(currentRecognizer.loadLabel(
- mFragment.getPackageManager()));
- mVoiceInteractionPref.setValue(currentSetting);
-
- if (settingsActivity == null) {
- // No settings preference available - hide the preference.
- Log.w(TAG, "no recognizer settings available for " + si.packageName);
- } else {
- Intent i = new Intent(Intent.ACTION_MAIN);
- i.setComponent(new ComponentName(si.packageName, settingsActivity));
- mVoiceInteractionSettingsPref.setIntent(i);
- }
- }
-
- private void populateRecognizerPreference(List<ResolveInfo> recognizers) {
- int size = recognizers.size();
- CharSequence[] entries = new CharSequence[size];
- CharSequence[] values = new CharSequence[size];
-
- // Get the current value from the secure setting.
- String currentSetting = Settings.Secure.getString(
- mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
-
- // Iterate through all the available recognizers and load up their info to show
- // in the preference. Also build up a map of recognizer component names to their
- // ResolveInfos - we'll need that a little later.
- for (int i = 0; i < size; i++) {
- ResolveInfo resolveInfo = recognizers.get(i);
- String recognizerComponent =
- new ComponentName(resolveInfo.serviceInfo.packageName,
- resolveInfo.serviceInfo.name).flattenToShortString();
-
- mAvailableRecognizersMap.put(recognizerComponent, resolveInfo);
-
- entries[i] = resolveInfo.loadLabel(mFragment.getPackageManager());
- values[i] = recognizerComponent;
- }
-
- mRecognizerPref.setEntries(entries);
- mRecognizerPref.setEntryValues(values);
-
- mRecognizerPref.setDefaultValue(currentSetting);
- mRecognizerPref.setValue(currentSetting);
-
- updateRecognizerSettingsLink(currentSetting);
- }
-
- private void updateRecognizerSettingsLink(String currentSetting) {
- ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting);
- if (currentRecognizer == null) return;
-
- ServiceInfo si = currentRecognizer.serviceInfo;
- XmlResourceParser parser = null;
- String settingsActivity = null;
- try {
- parser = si.loadXmlMetaData(mFragment.getPackageManager(),
- RecognitionService.SERVICE_META_DATA);
- if (parser == null) {
- throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
- " meta-data for " + si.packageName);
- }
-
- Resources res = mFragment.getPackageManager().getResourcesForApplication(
- si.applicationInfo);
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- }
-
- String nodeName = parser.getName();
- if (!"recognition-service".equals(nodeName)) {
- throw new XmlPullParserException(
- "Meta-data does not start with recognition-service tag");
- }
-
- TypedArray array = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.RecognitionService);
- settingsActivity = array.getString(
- com.android.internal.R.styleable.RecognitionService_settingsActivity);
- array.recycle();
- } catch (XmlPullParserException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (IOException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } finally {
- if (parser != null) parser.close();
- }
-
- if (settingsActivity == null) {
- // No settings preference available - hide the preference.
- Log.w(TAG, "no recognizer settings available for " + si.packageName);
- mRecognizerSettingsPref.setIntent(null);
- mVoiceCategory.removePreference(mRecognizerSettingsPref);
- } else {
- Intent i = new Intent(Intent.ACTION_MAIN);
- i.setComponent(new ComponentName(si.packageName, settingsActivity));
- mRecognizerSettingsPref.setIntent(i);
- mRecognizerPref.setSummary(currentRecognizer.loadLabel(mFragment.getPackageManager()));
- }
- }
-
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == mVoiceInteractionPref) {
- String setting = (String) newValue;
-
- // Put the new value back into secure settings.
- Settings.Secure.putString(mFragment.getContentResolver(),
- Settings.Secure.VOICE_INTERACTION_SERVICE,
- setting);
-
- // Update the settings item so it points to the right settings.
- updateVoiceInteractionSettingsLink(setting);
-
- } else if (preference == mRecognizerPref) {
- String setting = (String) newValue;
-
- // Put the new value back into secure settings.
- Settings.Secure.putString(mFragment.getContentResolver(),
- Settings.Secure.VOICE_RECOGNITION_SERVICE,
- setting);
-
- // Update the settings item so it points to the right settings.
- updateRecognizerSettingsLink(setting);
- }
- return true;
- }
}
--- /dev/null
+/*
+ * 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.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class VoiceInputHelper {
+ static final String TAG = "VoiceInputHelper";
+ final Context mContext;
+
+ final List<ResolveInfo> mAvailableVoiceInteractions;
+ final List<ResolveInfo> mAvailableRecognition;
+
+ static public class BaseInfo implements Comparable {
+ public final ServiceInfo service;
+ public final ComponentName componentName;
+ public final String key;
+ public final ComponentName settings;
+ public final CharSequence label;
+ public final String labelStr;
+ public final CharSequence appLabel;
+
+ public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) {
+ service = _service;
+ componentName = new ComponentName(_service.packageName, _service.name);
+ key = componentName.flattenToShortString();
+ settings = _settings != null
+ ? new ComponentName(_service.packageName, _settings) : null;
+ label = _service.loadLabel(pm);
+ labelStr = label.toString();
+ appLabel = _service.applicationInfo.loadLabel(pm);
+ }
+
+ @Override
+ public int compareTo(Object another) {
+ return labelStr.compareTo(((BaseInfo)another).labelStr);
+ }
+ }
+
+ static public class InteractionInfo extends BaseInfo {
+ public final VoiceInteractionServiceInfo serviceInfo;
+
+ public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) {
+ super(pm, _service.getServiceInfo(), _service.getSettingsActivity());
+ serviceInfo = _service;
+ }
+ }
+
+ static public class RecognizerInfo extends BaseInfo {
+ public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) {
+ super(pm, _service, _settings);
+ }
+ }
+
+ final ArrayList<InteractionInfo> mAvailableInteractionInfos = new ArrayList<>();
+ final ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>();
+
+ ComponentName mCurrentVoiceInteraction;
+ ComponentName mCurrentRecognizer;
+
+ public VoiceInputHelper(Context context) {
+ mContext = context;
+
+ mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+ mAvailableRecognition = mContext.getPackageManager().queryIntentServices(
+ new Intent(RecognitionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+ }
+
+ public boolean hasItems() {
+ return mAvailableVoiceInteractions.size() > 0 || mAvailableRecognition.size() > 0;
+ }
+
+ public void buildUi() {
+ // Get the currently selected interactor from the secure setting.
+ String currentSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
+ if (currentSetting != null && !currentSetting.isEmpty()) {
+ mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting);
+ } else {
+ mCurrentVoiceInteraction = null;
+ }
+
+ ArraySet<ComponentName> interactorRecognizers = new ArraySet<>();
+
+ // Iterate through all the available interactors and load up their info to show
+ // in the preference.
+ int size = mAvailableVoiceInteractions.size();
+ for (int i = 0; i < size; i++) {
+ ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i);
+ VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo(
+ mContext.getPackageManager(), resolveInfo.serviceInfo);
+ if (info.getParseError() != null) {
+ Log.w("VoiceInteractionService", "Error in VoiceInteractionService "
+ + resolveInfo.serviceInfo.packageName + "/"
+ + resolveInfo.serviceInfo.name + ": " + info.getParseError());
+ continue;
+ }
+ mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info));
+ if (info.getRecognitionService() != null) {
+ interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName,
+ info.getRecognitionService()));
+ }
+ }
+ Collections.sort(mAvailableInteractionInfos);
+
+ // Get the currently selected recognizer from the secure setting.
+ currentSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
+ if (currentSetting != null && !currentSetting.isEmpty()) {
+ mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting);
+ } else {
+ mCurrentRecognizer = null;
+ }
+
+ // Iterate through all the available recognizers and load up their info to show
+ // in the preference.
+ size = mAvailableRecognition.size();
+ for (int i = 0; i < size; i++) {
+ ResolveInfo resolveInfo = mAvailableRecognition.get(i);
+ ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ if (interactorRecognizers.contains(comp)) {
+ //continue;
+ }
+ ServiceInfo si = resolveInfo.serviceInfo;
+ XmlResourceParser parser = null;
+ String settingsActivity = null;
+ try {
+ parser = si.loadXmlMetaData(mContext.getPackageManager(),
+ RecognitionService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
+ " meta-data for " + si.packageName);
+ }
+
+ Resources res = mContext.getPackageManager().getResourcesForApplication(
+ si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"recognition-service".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with recognition-service tag");
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.RecognitionService);
+ settingsActivity = array.getString(
+ com.android.internal.R.styleable.RecognitionService_settingsActivity);
+ array.recycle();
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } catch (IOException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(),
+ resolveInfo.serviceInfo, settingsActivity));
+ }
+ Collections.sort(mAvailableRecognizerInfos);
+ }
+}
--- /dev/null
+/*
+ * 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) {
+ onRadioButtonClicked(rb, !rb.isChecked());
+ }
+ });
+
+ 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());
+ }
+
+ /**
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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.preference.Preference;
+import android.provider.Settings;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.voice.VoiceInputPreference.RadioButtonGroupState;
+
+import android.os.Bundle;
+import android.preference.PreferenceCategory;
+import android.widget.Checkable;
+
+public class VoiceInputSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceClickListener, RadioButtonGroupState {
+
+ 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
+ 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);
+ // Eventually we will require that an interactor always specify a recognizer
+ if (info.settings != null) {
+ Settings.Secure.putString(getActivity().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ info.settings.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, null);
+ 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;
+ }
+}