TtsEngines.java revision 4d03462b374dfc080f0c7c78d458c102a26be5c6
1a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich/* 2a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * Copyright (C) 2011 The Android Open Source Project 3a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * 4a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * use this file except in compliance with the License. You may obtain a copy of 6a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * the License at 7a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * 8a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * http://www.apache.org/licenses/LICENSE-2.0 9a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * 10a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * Unless required by applicable law or agreed to in writing, software 11a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * License for the specific language governing permissions and limitations under 14a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich * the License. 15a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich */ 16a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichpackage android.speech.tts; 17a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevich 18a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport org.xmlpull.v1.XmlPullParserException; 19655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Cheng 20655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.Context; 21655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.Intent; 22655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.pm.ApplicationInfo; 23a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport android.content.pm.PackageManager; 24655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.pm.PackageManager.NameNotFoundException; 25655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.pm.ResolveInfo; 26655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.pm.ServiceInfo; 27655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.res.Resources; 28a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport android.content.res.TypedArray; 29655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Chengimport android.content.res.XmlResourceParser; 30d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.provider.Settings; 31d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.speech.tts.TextToSpeech.Engine; 32d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.speech.tts.TextToSpeech.EngineInfo; 33a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport android.text.TextUtils; 34d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.util.AttributeSet; 35d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.util.Log; 36d7db594b8d1dab36b711bd887a9dd21675c87243Tao Baoimport android.util.Xml; 37655a7c081f83b8351ed5f11a6c6accd9458293a8Ben Cheng 38a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport java.io.IOException; 39a67e4de6620b570dabe0f92985228af0d0204f2cNick Kralevichimport java.util.ArrayList; 40import java.util.Collections; 41import java.util.Comparator; 42import java.util.List; 43 44/** 45 * Support class for querying the list of available engines 46 * on the device and deciding which one to use etc. 47 * 48 * Comments in this class the use the shorthand "system engines" for engines that 49 * are a part of the system image. 50 * 51 * @hide 52 */ 53public class TtsEngines { 54 private static final String TAG = "TtsEngines"; 55 56 private final Context mContext; 57 58 public TtsEngines(Context ctx) { 59 mContext = ctx; 60 } 61 62 /** 63 * @return the default TTS engine. If the user has set a default, and the engine 64 * is available on the device, the default is returned. Otherwise, 65 * the highest ranked engine is returned as per {@link EngineInfoComparator}. 66 */ 67 public String getDefaultEngine() { 68 String engine = Settings.Secure.getString(mContext.getContentResolver(), 69 Settings.Secure.TTS_DEFAULT_SYNTH); 70 return isEngineInstalled(engine) ? engine : getHighestRankedEngineName(); 71 } 72 73 /** 74 * @return the package name of the highest ranked system engine, {@code null} 75 * if no TTS engines were present in the system image. 76 */ 77 public String getHighestRankedEngineName() { 78 final List<EngineInfo> engines = getEngines(); 79 80 if (engines.size() > 0 && engines.get(0).system) { 81 return engines.get(0).name; 82 } 83 84 return null; 85 } 86 87 /** 88 * Returns the engine info for a given engine name. Note that engines are 89 * identified by their package name. 90 */ 91 public EngineInfo getEngineInfo(String packageName) { 92 PackageManager pm = mContext.getPackageManager(); 93 Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 94 intent.setPackage(packageName); 95 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 96 PackageManager.MATCH_DEFAULT_ONLY); 97 // Note that the current API allows only one engine per 98 // package name. Since the "engine name" is the same as 99 // the package name. 100 if (resolveInfos != null && resolveInfos.size() == 1) { 101 return getEngineInfo(resolveInfos.get(0), pm); 102 } 103 104 return null; 105 } 106 107 /** 108 * Gets a list of all installed TTS engines. 109 * 110 * @return A list of engine info objects. The list can be empty, but never {@code null}. 111 */ 112 public List<EngineInfo> getEngines() { 113 PackageManager pm = mContext.getPackageManager(); 114 Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 115 List<ResolveInfo> resolveInfos = 116 pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY); 117 if (resolveInfos == null) return Collections.emptyList(); 118 119 List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size()); 120 121 for (ResolveInfo resolveInfo : resolveInfos) { 122 EngineInfo engine = getEngineInfo(resolveInfo, pm); 123 if (engine != null) { 124 engines.add(engine); 125 } 126 } 127 Collections.sort(engines, EngineInfoComparator.INSTANCE); 128 129 return engines; 130 } 131 132 // TODO: Used only by the settings app. Remove once 133 // the settings UI change has been finalized. 134 public boolean isEngineEnabled(String engine) { 135 return isEngineInstalled(engine); 136 } 137 138 private boolean isSystemEngine(ServiceInfo info) { 139 final ApplicationInfo appInfo = info.applicationInfo; 140 return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 141 } 142 143 /** 144 * @return true if a given engine is installed on the system. 145 */ 146 public boolean isEngineInstalled(String engine) { 147 if (engine == null) { 148 return false; 149 } 150 151 return getEngineInfo(engine) != null; 152 } 153 154 /** 155 * @return an intent that can launch the settings activity for a given tts engine. 156 */ 157 public Intent getSettingsIntent(String engine) { 158 PackageManager pm = mContext.getPackageManager(); 159 Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 160 intent.setPackage(engine); 161 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 162 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA); 163 // Note that the current API allows only one engine per 164 // package name. Since the "engine name" is the same as 165 // the package name. 166 if (resolveInfos != null && resolveInfos.size() == 1) { 167 ServiceInfo service = resolveInfos.get(0).serviceInfo; 168 if (service != null) { 169 final String settings = settingsActivityFromServiceInfo(service, pm); 170 if (settings != null) { 171 Intent i = new Intent(); 172 i.setClassName(engine, settings); 173 return i; 174 } 175 } 176 } 177 178 return null; 179 } 180 181 /** 182 * The name of the XML tag that text to speech engines must use to 183 * declare their meta data. 184 * 185 * {@link com.android.internal.R.styleable.TextToSpeechEngine} 186 */ 187 private static final String XML_TAG_NAME = "tts-engine"; 188 189 private String settingsActivityFromServiceInfo(ServiceInfo si, PackageManager pm) { 190 XmlResourceParser parser = null; 191 try { 192 parser = si.loadXmlMetaData(pm, TextToSpeech.Engine.SERVICE_META_DATA); 193 if (parser == null) { 194 Log.w(TAG, "No meta-data found for :" + si); 195 return null; 196 } 197 198 final Resources res = pm.getResourcesForApplication(si.applicationInfo); 199 200 int type; 201 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) { 202 if (type == XmlResourceParser.START_TAG) { 203 if (!XML_TAG_NAME.equals(parser.getName())) { 204 Log.w(TAG, "Package " + si + " uses unknown tag :" 205 + parser.getName()); 206 return null; 207 } 208 209 final AttributeSet attrs = Xml.asAttributeSet(parser); 210 final TypedArray array = res.obtainAttributes(attrs, 211 com.android.internal.R.styleable.TextToSpeechEngine); 212 final String settings = array.getString( 213 com.android.internal.R.styleable.TextToSpeechEngine_settingsActivity); 214 array.recycle(); 215 216 return settings; 217 } 218 } 219 220 return null; 221 } catch (NameNotFoundException e) { 222 Log.w(TAG, "Could not load resources for : " + si); 223 return null; 224 } catch (XmlPullParserException e) { 225 Log.w(TAG, "Error parsing metadata for " + si + ":" + e); 226 return null; 227 } catch (IOException e) { 228 Log.w(TAG, "Error parsing metadata for " + si + ":" + e); 229 return null; 230 } finally { 231 if (parser != null) { 232 parser.close(); 233 } 234 } 235 } 236 237 private EngineInfo getEngineInfo(ResolveInfo resolve, PackageManager pm) { 238 ServiceInfo service = resolve.serviceInfo; 239 if (service != null) { 240 EngineInfo engine = new EngineInfo(); 241 // Using just the package name isn't great, since it disallows having 242 // multiple engines in the same package, but that's what the existing API does. 243 engine.name = service.packageName; 244 CharSequence label = service.loadLabel(pm); 245 engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString(); 246 engine.icon = service.getIconResource(); 247 engine.priority = resolve.priority; 248 engine.system = isSystemEngine(service); 249 return engine; 250 } 251 252 return null; 253 } 254 255 private static class EngineInfoComparator implements Comparator<EngineInfo> { 256 private EngineInfoComparator() { } 257 258 static EngineInfoComparator INSTANCE = new EngineInfoComparator(); 259 260 /** 261 * Engines that are a part of the system image are always lesser 262 * than those that are not. Within system engines / non system engines 263 * the engines are sorted in order of their declared priority. 264 */ 265 @Override 266 public int compare(EngineInfo lhs, EngineInfo rhs) { 267 if (lhs.system && !rhs.system) { 268 return -1; 269 } else if (rhs.system && !lhs.system) { 270 return 1; 271 } else { 272 // Either both system engines, or both non system 273 // engines. 274 // 275 // Note, this isn't a typo. Higher priority numbers imply 276 // higher priority, but are "lower" in the sort order. 277 return rhs.priority - lhs.priority; 278 } 279 } 280 } 281 282} 283