1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.settings; 18 19import android.content.ComponentName; 20import android.content.Intent; 21import android.content.pm.PackageManager; 22import android.content.pm.ResolveInfo; 23import android.content.pm.ServiceInfo; 24import android.content.pm.PackageManager.NameNotFoundException; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.content.res.XmlResourceParser; 28import android.preference.ListPreference; 29import android.preference.Preference; 30import android.preference.PreferenceCategory; 31import android.preference.PreferenceGroup; 32import android.preference.PreferenceScreen; 33import android.preference.Preference.OnPreferenceChangeListener; 34import android.provider.Settings; 35import android.speech.RecognitionService; 36import android.speech.tts.TtsEngines; 37import android.util.AttributeSet; 38import android.util.Log; 39import android.util.Xml; 40 41import java.io.IOException; 42import java.util.HashMap; 43import java.util.List; 44 45import org.xmlpull.v1.XmlPullParser; 46import org.xmlpull.v1.XmlPullParserException; 47 48/** 49 * Settings screen for voice input/output. 50 */ 51public class VoiceInputOutputSettings implements OnPreferenceChangeListener { 52 53 private static final String TAG = "VoiceInputOutputSettings"; 54 55 private static final String KEY_VOICE_CATEGORY = "voice_category"; 56 private static final String KEY_RECOGNIZER = "recognizer"; 57 private static final String KEY_RECOGNIZER_SETTINGS = "recognizer_settings"; 58 private static final String KEY_TTS_SETTINGS = "tts_settings"; 59 60 private PreferenceGroup mParent; 61 private PreferenceCategory mVoiceCategory; 62 private ListPreference mRecognizerPref; 63 private Preference mRecognizerSettingsPref; 64 private Preference mTtsSettingsPref; 65 private PreferenceScreen mSettingsPref; 66 private final SettingsPreferenceFragment mFragment; 67 private final TtsEngines mTtsEngines; 68 69 private HashMap<String, ResolveInfo> mAvailableRecognizersMap; 70 71 public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) { 72 mFragment = fragment; 73 mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext()); 74 } 75 76 public void onCreate() { 77 78 mParent = mFragment.getPreferenceScreen(); 79 mVoiceCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_CATEGORY); 80 mRecognizerPref = (ListPreference) mVoiceCategory.findPreference(KEY_RECOGNIZER); 81 mRecognizerSettingsPref = mVoiceCategory.findPreference(KEY_RECOGNIZER_SETTINGS); 82 mTtsSettingsPref = mVoiceCategory.findPreference(KEY_TTS_SETTINGS); 83 mRecognizerPref.setOnPreferenceChangeListener(this); 84 mSettingsPref = (PreferenceScreen) 85 mVoiceCategory.findPreference(KEY_RECOGNIZER_SETTINGS); 86 87 mAvailableRecognizersMap = new HashMap<String, ResolveInfo>(); 88 89 populateOrRemovePreferences(); 90 } 91 92 private void populateOrRemovePreferences() { 93 boolean hasRecognizerPrefs = populateOrRemoveRecognizerPrefs(); 94 boolean hasTtsPrefs = populateOrRemoveTtsPrefs(); 95 if (!hasRecognizerPrefs && !hasTtsPrefs) { 96 // There were no TTS settings and no recognizer settings, 97 // so it should be safe to hide the preference category 98 // entirely. 99 mFragment.getPreferenceScreen().removePreference(mVoiceCategory); 100 } 101 } 102 103 private boolean populateOrRemoveRecognizerPrefs() { 104 List<ResolveInfo> availableRecognitionServices = 105 mFragment.getPackageManager().queryIntentServices( 106 new Intent(RecognitionService.SERVICE_INTERFACE), 107 PackageManager.GET_META_DATA); 108 int numAvailable = availableRecognitionServices.size(); 109 110 if (numAvailable == 0) { 111 mVoiceCategory.removePreference(mRecognizerPref); 112 mVoiceCategory.removePreference(mRecognizerSettingsPref); 113 return false; 114 } 115 116 if (numAvailable == 1) { 117 // Only one recognizer available, so don't show the list of choices, but do 118 // set up the link to settings for the available recognizer. 119 mVoiceCategory.removePreference(mRecognizerPref); 120 121 // But first set up the available recognizers map with just the one recognizer. 122 ResolveInfo resolveInfo = availableRecognitionServices.get(0); 123 String recognizerComponent = 124 new ComponentName(resolveInfo.serviceInfo.packageName, 125 resolveInfo.serviceInfo.name).flattenToShortString(); 126 127 mAvailableRecognizersMap.put(recognizerComponent, resolveInfo); 128 129 String currentSetting = Settings.Secure.getString( 130 mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); 131 updateSettingsLink(currentSetting); 132 } else { 133 // Multiple recognizers available, so show the full list of choices. 134 populateRecognizerPreference(availableRecognitionServices); 135 } 136 137 // In this case, there was at least one available recognizer so 138 // we populated the settings. 139 return true; 140 } 141 142 private boolean populateOrRemoveTtsPrefs() { 143 if (mTtsEngines.getEngines().isEmpty()) { 144 mVoiceCategory.removePreference(mTtsSettingsPref); 145 return false; 146 } 147 148 return true; 149 } 150 151 private void populateRecognizerPreference(List<ResolveInfo> recognizers) { 152 int size = recognizers.size(); 153 CharSequence[] entries = new CharSequence[size]; 154 CharSequence[] values = new CharSequence[size]; 155 156 // Get the current value from the secure setting. 157 String currentSetting = Settings.Secure.getString( 158 mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); 159 160 // Iterate through all the available recognizers and load up their info to show 161 // in the preference. Also build up a map of recognizer component names to their 162 // ResolveInfos - we'll need that a little later. 163 for (int i = 0; i < size; i++) { 164 ResolveInfo resolveInfo = recognizers.get(i); 165 String recognizerComponent = 166 new ComponentName(resolveInfo.serviceInfo.packageName, 167 resolveInfo.serviceInfo.name).flattenToShortString(); 168 169 mAvailableRecognizersMap.put(recognizerComponent, resolveInfo); 170 171 entries[i] = resolveInfo.loadLabel(mFragment.getPackageManager()); 172 values[i] = recognizerComponent; 173 } 174 175 mRecognizerPref.setEntries(entries); 176 mRecognizerPref.setEntryValues(values); 177 178 mRecognizerPref.setDefaultValue(currentSetting); 179 mRecognizerPref.setValue(currentSetting); 180 181 updateSettingsLink(currentSetting); 182 } 183 184 private void updateSettingsLink(String currentSetting) { 185 ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting); 186 if (currentRecognizer == null) return; 187 188 ServiceInfo si = currentRecognizer.serviceInfo; 189 XmlResourceParser parser = null; 190 String settingsActivity = null; 191 try { 192 parser = si.loadXmlMetaData(mFragment.getPackageManager(), 193 RecognitionService.SERVICE_META_DATA); 194 if (parser == null) { 195 throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + 196 " meta-data for " + si.packageName); 197 } 198 199 Resources res = mFragment.getPackageManager().getResourcesForApplication( 200 si.applicationInfo); 201 202 AttributeSet attrs = Xml.asAttributeSet(parser); 203 204 int type; 205 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 206 && type != XmlPullParser.START_TAG) { 207 } 208 209 String nodeName = parser.getName(); 210 if (!"recognition-service".equals(nodeName)) { 211 throw new XmlPullParserException( 212 "Meta-data does not start with recognition-service tag"); 213 } 214 215 TypedArray array = res.obtainAttributes(attrs, 216 com.android.internal.R.styleable.RecognitionService); 217 settingsActivity = array.getString( 218 com.android.internal.R.styleable.RecognitionService_settingsActivity); 219 array.recycle(); 220 } catch (XmlPullParserException e) { 221 Log.e(TAG, "error parsing recognition service meta-data", e); 222 } catch (IOException e) { 223 Log.e(TAG, "error parsing recognition service meta-data", e); 224 } catch (NameNotFoundException e) { 225 Log.e(TAG, "error parsing recognition service meta-data", e); 226 } finally { 227 if (parser != null) parser.close(); 228 } 229 230 if (settingsActivity == null) { 231 // No settings preference available - hide the preference. 232 Log.w(TAG, "no recognizer settings available for " + si.packageName); 233 mSettingsPref.setIntent(null); 234 mVoiceCategory.removePreference(mSettingsPref); 235 } else { 236 Intent i = new Intent(Intent.ACTION_MAIN); 237 i.setComponent(new ComponentName(si.packageName, settingsActivity)); 238 mSettingsPref.setIntent(i); 239 mRecognizerPref.setSummary(currentRecognizer.loadLabel(mFragment.getPackageManager())); 240 } 241 } 242 243 public boolean onPreferenceChange(Preference preference, Object newValue) { 244 if (preference == mRecognizerPref) { 245 String setting = (String) newValue; 246 247 // Put the new value back into secure settings. 248 Settings.Secure.putString(mFragment.getContentResolver(), 249 Settings.Secure.VOICE_RECOGNITION_SERVICE, 250 setting); 251 252 // Update the settings item so it points to the right settings. 253 updateSettingsLink(setting); 254 } 255 return true; 256 } 257} 258