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 17 package com.android.settings; 18 19 import android.content.ComponentName; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.content.pm.ResolveInfo; 23 import android.content.pm.ServiceInfo; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.XmlResourceParser; 28 import android.preference.ListPreference; 29 import android.preference.Preference; 30 import android.preference.PreferenceCategory; 31 import android.preference.PreferenceGroup; 32 import android.preference.PreferenceScreen; 33 import android.preference.Preference.OnPreferenceChangeListener; 34 import android.provider.Settings; 35 import android.speech.RecognitionService; 36 import android.speech.tts.TtsEngines; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.util.Xml; 40 41 import java.io.IOException; 42 import java.util.HashMap; 43 import java.util.List; 44 45 import org.xmlpull.v1.XmlPullParser; 46 import org.xmlpull.v1.XmlPullParserException; 47 48 /** 49 * Settings screen for voice input/output. 50 */ 51 public 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 VoiceInputOutputSettings(SettingsPreferenceFragment fragment)71 public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) { 72 mFragment = fragment; 73 mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext()); 74 } 75 onCreate()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 populateOrRemovePreferences()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 populateOrRemoveRecognizerPrefs()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 populateOrRemoveTtsPrefs()142 private boolean populateOrRemoveTtsPrefs() { 143 if (mTtsEngines.getEngines().isEmpty()) { 144 mVoiceCategory.removePreference(mTtsSettingsPref); 145 return false; 146 } 147 148 return true; 149 } 150 populateRecognizerPreference(List<ResolveInfo> recognizers)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 updateSettingsLink(String currentSetting)184 private void updateSettingsLink(String currentSetting) { 185 ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting); 186 ServiceInfo si = currentRecognizer.serviceInfo; 187 XmlResourceParser parser = null; 188 String settingsActivity = null; 189 try { 190 parser = si.loadXmlMetaData(mFragment.getPackageManager(), 191 RecognitionService.SERVICE_META_DATA); 192 if (parser == null) { 193 throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + 194 " meta-data for " + si.packageName); 195 } 196 197 Resources res = mFragment.getPackageManager().getResourcesForApplication( 198 si.applicationInfo); 199 200 AttributeSet attrs = Xml.asAttributeSet(parser); 201 202 int type; 203 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 204 && type != XmlPullParser.START_TAG) { 205 } 206 207 String nodeName = parser.getName(); 208 if (!"recognition-service".equals(nodeName)) { 209 throw new XmlPullParserException( 210 "Meta-data does not start with recognition-service tag"); 211 } 212 213 TypedArray array = res.obtainAttributes(attrs, 214 com.android.internal.R.styleable.RecognitionService); 215 settingsActivity = array.getString( 216 com.android.internal.R.styleable.RecognitionService_settingsActivity); 217 array.recycle(); 218 } catch (XmlPullParserException e) { 219 Log.e(TAG, "error parsing recognition service meta-data", e); 220 } catch (IOException e) { 221 Log.e(TAG, "error parsing recognition service meta-data", e); 222 } catch (NameNotFoundException e) { 223 Log.e(TAG, "error parsing recognition service meta-data", e); 224 } finally { 225 if (parser != null) parser.close(); 226 } 227 228 if (settingsActivity == null) { 229 // No settings preference available - hide the preference. 230 Log.w(TAG, "no recognizer settings available for " + si.packageName); 231 mSettingsPref.setIntent(null); 232 mVoiceCategory.removePreference(mSettingsPref); 233 } else { 234 Intent i = new Intent(Intent.ACTION_MAIN); 235 i.setComponent(new ComponentName(si.packageName, settingsActivity)); 236 mSettingsPref.setIntent(i); 237 mRecognizerPref.setSummary(currentRecognizer.loadLabel(mFragment.getPackageManager())); 238 } 239 } 240 onPreferenceChange(Preference preference, Object newValue)241 public boolean onPreferenceChange(Preference preference, Object newValue) { 242 if (preference == mRecognizerPref) { 243 String setting = (String) newValue; 244 245 // Put the new value back into secure settings. 246 Settings.Secure.putString(mFragment.getContentResolver(), 247 Settings.Secure.VOICE_RECOGNITION_SERVICE, 248 setting); 249 250 // Update the settings item so it points to the right settings. 251 updateSettingsLink(setting); 252 } 253 return true; 254 } 255 } 256