TtsEnginespublic class TtsEngines extends Object Support class for querying the list of available engines
on the device and deciding which one to use etc.
Comments in this class the use the shorthand "system engines" for engines that
are a part of the system image.
This class is thread-safe/ |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DBG | private static final String | LOCALE_DELIMITER_OLDLocale delimiter used by the old-style 3 char locale string format (like "eng-usa") | private static final String | LOCALE_DELIMITER_NEWLocale delimiter used by the new-style locale string format (Locale.toString() results,
like "en_US") | private final android.content.Context | mContext | private static final Map | sNormalizeLanguageMapping of various language strings to the normalized Locale form | private static final Map | sNormalizeCountryMapping of various country strings to the normalized Locale form | private static final String | XML_TAG_NAMEThe name of the XML tag that text to speech engines must use to
declare their meta data.
{@link com.android.internal.R.styleable#TextToSpeechEngine} |
Constructors Summary |
---|
public TtsEngines(android.content.Context ctx)
// Populate the sNormalize* maps
HashMap<String, String> normalizeLanguage = new HashMap<String, String>();
for (String language : Locale.getISOLanguages()) {
try {
normalizeLanguage.put(new Locale(language).getISO3Language(), language);
} catch (MissingResourceException e) {
continue;
}
}
sNormalizeLanguage = Collections.unmodifiableMap(normalizeLanguage);
HashMap<String, String> normalizeCountry = new HashMap<String, String>();
for (String country : Locale.getISOCountries()) {
try {
normalizeCountry.put(new Locale("", country).getISO3Country(), country);
} catch (MissingResourceException e) {
continue;
}
}
sNormalizeCountry = Collections.unmodifiableMap(normalizeCountry);
mContext = ctx;
|
Methods Summary |
---|
public java.lang.String | getDefaultEngine()
String engine = getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_SYNTH);
return isEngineInstalled(engine) ? engine : getHighestRankedEngineName();
| private android.speech.tts.TextToSpeech.EngineInfo | getEngineInfo(android.content.pm.ResolveInfo resolve, android.content.pm.PackageManager pm)
ServiceInfo service = resolve.serviceInfo;
if (service != null) {
EngineInfo engine = new EngineInfo();
// Using just the package name isn't great, since it disallows having
// multiple engines in the same package, but that's what the existing API does.
engine.name = service.packageName;
CharSequence label = service.loadLabel(pm);
engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
engine.icon = service.getIconResource();
engine.priority = resolve.priority;
engine.system = isSystemEngine(service);
return engine;
}
return null;
| public android.speech.tts.TextToSpeech.EngineInfo | getEngineInfo(java.lang.String packageName)Returns the engine info for a given engine name. Note that engines are
identified by their package name.
PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
intent.setPackage(packageName);
List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
PackageManager.MATCH_DEFAULT_ONLY);
// Note that the current API allows only one engine per
// package name. Since the "engine name" is the same as
// the package name.
if (resolveInfos != null && resolveInfos.size() == 1) {
return getEngineInfo(resolveInfos.get(0), pm);
}
return null;
| public java.util.List | getEngines()Gets a list of all installed TTS engines.
PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
List<ResolveInfo> resolveInfos =
pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfos == null) return Collections.emptyList();
List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
for (ResolveInfo resolveInfo : resolveInfos) {
EngineInfo engine = getEngineInfo(resolveInfo, pm);
if (engine != null) {
engines.add(engine);
}
}
Collections.sort(engines, EngineInfoComparator.INSTANCE);
return engines;
| public java.lang.String | getHighestRankedEngineName()
final List<EngineInfo> engines = getEngines();
if (engines.size() > 0 && engines.get(0).system) {
return engines.get(0).name;
}
return null;
| public java.util.Locale | getLocalePrefForEngine(java.lang.String engineName)Returns the default locale for a given TTS engine. Attempts to read the
value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
default phone locale is returned.
return getLocalePrefForEngine(engineName,
getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE));
| public java.util.Locale | getLocalePrefForEngine(java.lang.String engineName, java.lang.String prefValue)Returns the default locale for a given TTS engine from given settings string.
String localeString = parseEnginePrefFromList(
prefValue,
engineName);
if (TextUtils.isEmpty(localeString)) {
// The new style setting is unset, attempt to return the old style setting.
return Locale.getDefault();
}
Locale result = parseLocaleString(localeString);
if (result == null) {
Log.w(TAG, "Failed to parse locale " + localeString + ", returning en_US instead");
result = Locale.US;
}
if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + result);
return result;
| public android.content.Intent | getSettingsIntent(java.lang.String engine)
PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
intent.setPackage(engine);
List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA);
// Note that the current API allows only one engine per
// package name. Since the "engine name" is the same as
// the package name.
if (resolveInfos != null && resolveInfos.size() == 1) {
ServiceInfo service = resolveInfos.get(0).serviceInfo;
if (service != null) {
final String settings = settingsActivityFromServiceInfo(service, pm);
if (settings != null) {
Intent i = new Intent();
i.setClassName(engine, settings);
return i;
}
}
}
return null;
| public boolean | isEngineInstalled(java.lang.String engine)
if (engine == null) {
return false;
}
return getEngineInfo(engine) != null;
| public boolean | isLocaleSetToDefaultForEngine(java.lang.String engineName)True if a given TTS engine uses the default phone locale as a default locale. Attempts to
read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}. If
its value is empty, this methods returns true.
return TextUtils.isEmpty(parseEnginePrefFromList(
getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
engineName));
| private boolean | isSystemEngine(android.content.pm.ServiceInfo info)
final ApplicationInfo appInfo = info.applicationInfo;
return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
| public static java.util.Locale | normalizeTTSLocale(java.util.Locale ttsLocale)This method tries its best to return a valid {@link Locale} object from the TTS-specific
Locale input (returned by {@link TextToSpeech#getLanguage}
and {@link TextToSpeech#getDefaultLanguage}). A TTS Locale language field contains
a three-letter ISO 639-2/T code (where a proper Locale would use a two-letter ISO 639-1
code), and the country field contains a three-letter ISO 3166 country code (where a proper
Locale would use a two-letter ISO 3166-1 code).
This method tries to convert three-letter language and country codes into their two-letter
equivalents. If it fails to do so, it keeps the value from the TTS locale.
String language = ttsLocale.getLanguage();
if (!TextUtils.isEmpty(language)) {
String normalizedLanguage = sNormalizeLanguage.get(language);
if (normalizedLanguage != null) {
language = normalizedLanguage;
}
}
String country = ttsLocale.getCountry();
if (!TextUtils.isEmpty(country)) {
String normalizedCountry= sNormalizeCountry.get(country);
if (normalizedCountry != null) {
country = normalizedCountry;
}
}
return new Locale(language, country, ttsLocale.getVariant());
| private static java.lang.String | parseEnginePrefFromList(java.lang.String prefValue, java.lang.String engineName)Parses a comma separated list of engine locale preferences. The list is of the
form {@code "engine_name_1:locale_1,engine_name_2:locale2"} and so on and
so forth. Returns null if the list is empty, malformed or if there is no engine
specific preference in the list.
if (TextUtils.isEmpty(prefValue)) {
return null;
}
String[] prefValues = prefValue.split(",");
for (String value : prefValues) {
final int delimiter = value.indexOf(':");
if (delimiter > 0) {
if (engineName.equals(value.substring(0, delimiter))) {
return value.substring(delimiter + 1);
}
}
}
return null;
| public java.util.Locale | parseLocaleString(java.lang.String localeString)Parses a locale encoded as a string, and tries its best to return a valid {@link Locale}
object, even if the input string is encoded using the old-style 3 character format e.g.
"deu-deu". At the end, we test if the resulting locale can return ISO3 language and
country codes ({@link Locale#getISO3Language()} and {@link Locale#getISO3Country()}),
if it fails to do so, we return null.
String language = "", country = "", variant = "";
if (!TextUtils.isEmpty(localeString)) {
String[] split = localeString.split(
"[" + LOCALE_DELIMITER_OLD + LOCALE_DELIMITER_NEW + "]");
language = split[0].toLowerCase();
if (split.length == 0) {
Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Only" +
" separators");
return null;
}
if (split.length > 3) {
Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Too" +
" many separators");
return null;
}
if (split.length >= 2) {
country = split[1].toUpperCase();
}
if (split.length >= 3) {
variant = split[2];
}
}
String normalizedLanguage = sNormalizeLanguage.get(language);
if (normalizedLanguage != null) {
language = normalizedLanguage;
}
String normalizedCountry= sNormalizeCountry.get(country);
if (normalizedCountry != null) {
country = normalizedCountry;
}
if (DBG) Log.d(TAG, "parseLocalePref(" + language + "," + country +
"," + variant +")");
Locale result = new Locale(language, country, variant);
try {
result.getISO3Language();
result.getISO3Country();
return result;
} catch(MissingResourceException e) {
Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object.");
return null;
}
| private java.lang.String | settingsActivityFromServiceInfo(android.content.pm.ServiceInfo si, android.content.pm.PackageManager pm)
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, TextToSpeech.Engine.SERVICE_META_DATA);
if (parser == null) {
Log.w(TAG, "No meta-data found for :" + si);
return null;
}
final Resources res = pm.getResourcesForApplication(si.applicationInfo);
int type;
while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
if (type == XmlResourceParser.START_TAG) {
if (!XML_TAG_NAME.equals(parser.getName())) {
Log.w(TAG, "Package " + si + " uses unknown tag :"
+ parser.getName());
return null;
}
final AttributeSet attrs = Xml.asAttributeSet(parser);
final TypedArray array = res.obtainAttributes(attrs,
com.android.internal.R.styleable.TextToSpeechEngine);
final String settings = array.getString(
com.android.internal.R.styleable.TextToSpeechEngine_settingsActivity);
array.recycle();
return settings;
}
}
return null;
} catch (NameNotFoundException e) {
Log.w(TAG, "Could not load resources for : " + si);
return null;
} catch (XmlPullParserException e) {
Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
return null;
} catch (IOException e) {
Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
return null;
} finally {
if (parser != null) {
parser.close();
}
}
| public static java.lang.String[] | toOldLocaleStringFormat(java.util.Locale locale)Return the old-style string form of the locale. It consists of 3 letter codes:
- "ISO 639-2/T language code" if the locale has no country entry
- "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code"
if the locale has no variant entry
- "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country
code{@link #LOCALE_DELIMITER}variant" if the locale has a variant entry
If we fail to generate those codes using {@link Locale#getISO3Country()} and
{@link Locale#getISO3Language()}, then we return new String[]{"eng","USA",""};
String[] ret = new String[]{"","",""};
try {
// Note that the default locale might have an empty variant
// or language, and we take care that the construction is
// the same as {@link #getV1Locale} i.e no trailing delimiters
// or spaces.
ret[0] = locale.getISO3Language();
ret[1] = locale.getISO3Country();
ret[2] = locale.getVariant();
return ret;
} catch (MissingResourceException e) {
// Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the
// default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US).
return new String[]{"eng","USA",""};
}
| public synchronized void | updateLocalePrefForEngine(java.lang.String engineName, java.util.Locale newLocale)Serialize the locale to a string and store it as a default locale for the given engine. If
the passed locale is null, an empty string will be serialized; that empty string, when
read back, will evaluate to {@link Locale#getDefault()}.
final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_LOCALE);
if (DBG) {
Log.d(TAG, "updateLocalePrefForEngine(" + engineName + ", " + newLocale +
"), originally: " + prefList);
}
final String newPrefList = updateValueInCommaSeparatedList(prefList,
engineName, (newLocale != null) ? newLocale.toString() : "");
if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_LOCALE, newPrefList.toString());
| private java.lang.String | updateValueInCommaSeparatedList(java.lang.String list, java.lang.String key, java.lang.String newValue)Updates the value for a given key in a comma separated list of key value pairs,
each of which are delimited by a colon. If no value exists for the given key,
the kay value pair are appended to the end of the list.
StringBuilder newPrefList = new StringBuilder();
if (TextUtils.isEmpty(list)) {
// If empty, create a new list with a single entry.
newPrefList.append(key).append(':").append(newValue);
} else {
String[] prefValues = list.split(",");
// Whether this is the first iteration in the loop.
boolean first = true;
// Whether we found the given key.
boolean found = false;
for (String value : prefValues) {
final int delimiter = value.indexOf(':");
if (delimiter > 0) {
if (key.equals(value.substring(0, delimiter))) {
if (first) {
first = false;
} else {
newPrefList.append(',");
}
found = true;
newPrefList.append(key).append(':").append(newValue);
} else {
if (first) {
first = false;
} else {
newPrefList.append(',");
}
// Copy across the entire key + value as is.
newPrefList.append(value);
}
}
}
if (!found) {
// Not found, but the rest of the keys would have been copied
// over already, so just append it to the end.
newPrefList.append(',");
newPrefList.append(key).append(':").append(newValue);
}
}
return newPrefList.toString();
|
|