diff --git a/res/values/strings.xml b/res/values/strings.xml index a16c7912778..bbce4e4211c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4061,24 +4061,28 @@ Android keyboard Speech - - Voice input - - Voice input - - Settings for - \'%s\' - - None - - Voice recognizer - - Voice Search - - Settings for \'%s\' + + + + Voice input settings + + Voice input + + Voice input services + + Full voice interaction + + Simple voice recognition + + This voice input service will be able to + control all voice enabled applications on your behalf. + It comes from the %s application. + Enable the use of this service? @@ -5462,6 +5466,7 @@ space disk hard drive power spelling + recognizer input speech speak language hands-free hand free recognition offensive word audio history bluetooth headset rate language default speak speaking clock wipe delete @@ -5474,7 +5479,6 @@ account restriction restrict restricted text correction correct sound vibrate auto language gesture suggest suggestion theme offensive word type emoji - language hands-free hand free recognition offensive word audio history bluetooth headset diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml index b493a0ff86b..9d12857f877 100644 --- a/res/xml/language_settings.xml +++ b/res/xml/language_settings.xml @@ -99,31 +99,11 @@ android:key="voice_category" android:title="@string/voice_category" > - - - - - - - - - - + + + + + + + + diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java index 64f8a09d6f4..e052f8ebf0f 100644 --- a/src/com/android/settings/VoiceInputOutputSettings.java +++ b/src/com/android/settings/VoiceInputOutputSettings.java @@ -16,65 +16,31 @@ 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 mAvailableVoiceInteractionsMap; - - private HashMap mAvailableRecognizersMap; - public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) { mFragment = fragment; mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext()); @@ -84,28 +50,16 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener { 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(); - mAvailableRecognizersMap = new HashMap(); - 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. @@ -113,68 +67,13 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener { } } - private boolean populateOrRemoveVoiceInteractionPrefs() { - List availableVoiceServices = - mFragment.getPackageManager().queryIntentServices( - new Intent(VoiceInteractionService.SERVICE_INTERFACE), - PackageManager.GET_META_DATA); - for (int i=0; i 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; } @@ -186,222 +85,4 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener { return true; } - - private void populateVoiceInteractionPreference(List 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 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; - } } diff --git a/src/com/android/settings/voice/VoiceInputHelper.java b/src/com/android/settings/voice/VoiceInputHelper.java new file mode 100644 index 00000000000..63b891af833 --- /dev/null +++ b/src/com/android/settings/voice/VoiceInputHelper.java @@ -0,0 +1,211 @@ +/* + * 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 mAvailableVoiceInteractions; + final List 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 mAvailableInteractionInfos = new ArrayList<>(); + final ArrayList 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 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); + } +} diff --git a/src/com/android/settings/voice/VoiceInputPreference.java b/src/com/android/settings/voice/VoiceInputPreference.java new file mode 100644 index 00000000000..0ebffbbbf85 --- /dev/null +++ b/src/com/android/settings/voice/VoiceInputPreference.java @@ -0,0 +1,233 @@ +/* + * 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); + } +} diff --git a/src/com/android/settings/voice/VoiceInputSettings.java b/src/com/android/settings/voice/VoiceInputSettings.java new file mode 100644 index 00000000000..309c6e9ba0c --- /dev/null +++ b/src/com/android/settings/voice/VoiceInputSettings.java @@ -0,0 +1,163 @@ +/* + * 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