Merge "Expose "default tts locale" to the TTS V2 API."

This commit is contained in:
Przemyslaw Szczepaniak
2014-06-13 12:04:00 +00:00
committed by Android (Google) Code Review
7 changed files with 243 additions and 115 deletions

View File

@@ -26522,6 +26522,7 @@ package android.speech.tts {
}
public static final class TextToSpeechClient.EngineStatus {
method public java.util.Locale getDefaultLocale();
method public java.lang.String getEnginePackage();
method public java.util.List<android.speech.tts.VoiceInfo> getVoices();
}

View File

@@ -46,7 +46,8 @@ public final class RequestConfigHelper {
/**
* Score positively voices that exactly match the given locale
* @param locale Reference locale. If null, the default locale will be used.
* @param locale Reference locale. If null, the system default locale for the
* current user will be used ({@link Locale#getDefault()}).
*/
public ExactLocaleMatcher(Locale locale) {
if (locale == null) {
@@ -70,7 +71,8 @@ public final class RequestConfigHelper {
/**
* Score positively voices with similar locale.
* @param locale Reference locale. If null, default will be used.
* @param locale Reference locale. If null, the system default locale for the
* current user will be used ({@link Locale#getDefault()}).
*/
public LanguageMatcher(Locale locale) {
if (locale == null) {
@@ -164,10 +166,10 @@ public final class RequestConfigHelper {
}
/**
* Get highest quality voice for the default locale.
* Get highest quality voice for the TTS default locale.
*
* Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with
* {@link LanguageMatcher} set to device default locale.
* {@link LanguageMatcher} set to the {@link EngineStatus#getDefaultLocale()}.
*
* @param engineStatus
* Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
@@ -179,7 +181,7 @@ public final class RequestConfigHelper {
public static RequestConfig highestQuality(EngineStatus engineStatus,
boolean hasToBeEmbedded) {
return highestQuality(engineStatus, hasToBeEmbedded,
new LanguageMatcher(Locale.getDefault()));
new LanguageMatcher(engineStatus.getDefaultLocale()));
}
}

View File

@@ -167,9 +167,7 @@ public final class SynthesisRequestV2 implements Parcelable {
}
};
/**
* @hide
*/
/** @hide */
@Override
public int describeContents() {
return 0;

View File

@@ -42,6 +42,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -357,9 +358,13 @@ public class TextToSpeechClient {
/** Name of the TTS engine package */
private final String mPackageName;
private EngineStatus(String packageName, List<VoiceInfo> voices) {
/** Engine default locale */
private final Locale mDefaultLocale;
private EngineStatus(String packageName, List<VoiceInfo> voices, Locale defaultLocale) {
this.mVoices = Collections.unmodifiableList(voices);
this.mPackageName = packageName;
this.mDefaultLocale = defaultLocale;
}
/**
@@ -375,6 +380,16 @@ public class TextToSpeechClient {
public String getEnginePackage() {
return mPackageName;
}
/**
* Get the default locale to use for TTS with this TTS engine.
* Unless the user changed the TTS settings for this engine, the value returned should be
* the same as the system default locale for the current user
* ({@link Locale#getDefault()}).
*/
public Locale getDefaultLocale() {
return mDefaultLocale;
}
}
/** Unique synthesis request identifier. */
@@ -638,7 +653,9 @@ public class TextToSpeechClient {
return null;
}
return new EngineStatus(mServiceConnection.getEngineName(), voices);
return new EngineStatus(mServiceConnection.getEngineName(), voices,
mEnginesHelper.getLocalePrefForEngine(
mServiceConnection.getEngineName()));
}
private class Connection implements ServiceConnection {
@@ -696,7 +713,9 @@ public class TextToSpeechClient {
public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) {
synchronized (mLock) {
mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(),
voicesInfo);
voicesInfo,
mEnginesHelper.getLocalePrefForEngine(
mServiceConnection.getEngineName()));
mMainHandler.obtainMessage(InternalHandler.WHAT_ENGINE_STATUS_CHANGED,
mEngineStatus).sendToTarget();
}

View File

@@ -460,8 +460,8 @@ public abstract class TextToSpeechService extends Service {
}
private String[] getSettingsLocale() {
final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
return TtsEngines.parseLocalePref(locale);
final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
return TtsEngines.toOldLocaleStringFormat(locale);
}
private int getSecureSettingInt(String name, int defaultValue) {

View File

@@ -28,6 +28,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import static android.provider.Settings.Secure.getString;
import android.provider.Settings;
@@ -42,8 +43,10 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
/**
@@ -53,16 +56,52 @@ import java.util.MissingResourceException;
* 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/
*
* @hide
*/
public class TtsEngines {
private static final String TAG = "TtsEngines";
private static final boolean DBG = false;
private static final String LOCALE_DELIMITER = "-";
/** Locale delimiter used by the old-style 3 char locale string format (like "eng-usa") */
private static final String LOCALE_DELIMITER_OLD = "-";
/** Locale delimiter used by the new-style locale string format (Locale.toString() results,
* like "en_US") */
private static final String LOCALE_DELIMITER_NEW = "_";
private final Context mContext;
/** Mapping of various language strings to the normalized Locale form */
private static final Map<String, String> sNormalizeLanguage;
/** Mapping of various country strings to the normalized Locale form */
private static final Map<String, String> sNormalizeCountry;
// Populate the sNormalize* maps
static {
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);
}
public TtsEngines(Context ctx) {
mContext = ctx;
}
@@ -282,139 +321,139 @@ public class TtsEngines {
}
/**
* Returns the locale string for a given TTS engine. Attempts to read the
* Returns the default locale for a given TTS engine. Attempts to read the
* value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
* old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
* both these values are empty, the default phone locale is returned.
* default phone locale is returned.
*
* @param engineName the engine to return the locale for.
* @return the locale string preference for this engine. Will be non null
* and non empty.
* @return the locale preference for this engine. Will be non null.
*/
public String getLocalePrefForEngine(String engineName) {
String locale = parseEnginePrefFromList(
getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
engineName);
if (TextUtils.isEmpty(locale)) {
// The new style setting is unset, attempt to return the old style setting.
locale = getV1Locale();
}
if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + locale);
return locale;
public Locale getLocalePrefForEngine(String engineName) {
return getLocalePrefForEngine(engineName,
getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE));
}
/**
* Returns the default locale for a given TTS engine from given settings string. */
public Locale getLocalePrefForEngine(String engineName, String prefValue) {
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;
}
/**
* 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}, failing which the
* old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
* both these values are empty, this methods returns true.
* read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}. If
* its value is empty, this methods returns true.
*
* @param engineName the engine to return the locale for.
*/
public boolean isLocaleSetToDefaultForEngine(String engineName) {
return (TextUtils.isEmpty(parseEnginePrefFromList(
return TextUtils.isEmpty(parseEnginePrefFromList(
getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
engineName)) &&
TextUtils.isEmpty(
Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_LANG)));
engineName));
}
/**
* Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}.
* Varies from {@link String#split} in that it will always return an array
* of length 3 with non null values.
* 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.
*/
public static String[] parseLocalePref(String pref) {
String[] returnVal = new String[] { "", "", ""};
if (!TextUtils.isEmpty(pref)) {
String[] split = pref.split(LOCALE_DELIMITER);
System.arraycopy(split, 0, returnVal, 0, split.length);
public Locale parseLocaleString(String localeString) {
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];
}
}
if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] +
"," + returnVal[2] +")");
String normalizedLanguage = sNormalizeLanguage.get(language);
if (normalizedLanguage != null) {
language = normalizedLanguage;
}
return returnVal;
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;
}
}
/**
* @return the old style locale string constructed from
* {@link Settings.Secure#TTS_DEFAULT_LANG},
* {@link Settings.Secure#TTS_DEFAULT_COUNTRY} and
* {@link Settings.Secure#TTS_DEFAULT_VARIANT}. If no such locale is set,
* then return the default phone locale.
*/
private String getV1Locale() {
final ContentResolver cr = mContext.getContentResolver();
final String lang = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_LANG);
final String country = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_COUNTRY);
final String variant = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_VARIANT);
if (TextUtils.isEmpty(lang)) {
return getDefaultLocale();
}
String v1Locale = lang;
if (!TextUtils.isEmpty(country)) {
v1Locale += LOCALE_DELIMITER + country;
} else {
return v1Locale;
}
if (!TextUtils.isEmpty(variant)) {
v1Locale += LOCALE_DELIMITER + variant;
}
return v1Locale;
}
/**
* Return the default device locale in form of 3 letter codes delimited by
* {@link #LOCALE_DELIMITER}:
* Return the old-style string form of the locale. It consists of 3 letter codes:
* <ul>
* <li> "ISO 639-2/T language code" if locale have no country entry</li>
* <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code "
* if locale have no variant entry</li>
* <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code
* {@link #LOCALE_DELIMITER} variant" if locale have variant entry</li>
* <li>"ISO 639-2/T language code" if the locale has no country entry</li>
* <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code"
* if the locale has no variant entry</li>
* <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country
* code{@link #LOCALE_DELIMITER}variant" if the locale has a variant entry</li>
* </ul>
* If we fail to generate those codes using {@link Locale#getISO3Country()} and
* {@link Locale#getISO3Language()}, then we return new String[]{"eng","USA",""};
*/
public String getDefaultLocale() {
final Locale locale = Locale.getDefault();
static public String[] toOldLocaleStringFormat(Locale locale) {
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.
String defaultLocale = locale.getISO3Language();
if (TextUtils.isEmpty(defaultLocale)) {
Log.w(TAG, "Default locale is empty.");
return "";
}
ret[0] = locale.getISO3Language();
ret[1] = locale.getISO3Country();
ret[2] = locale.getVariant();
if (!TextUtils.isEmpty(locale.getISO3Country())) {
defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
} else {
// Do not allow locales of the form lang--variant with
// an empty country.
return defaultLocale;
}
if (!TextUtils.isEmpty(locale.getVariant())) {
defaultLocale += LOCALE_DELIMITER + locale.getVariant();
}
return defaultLocale;
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 "eng-usa";
return new String[]{"eng","USA",""};
}
}
@@ -443,16 +482,21 @@ public class TtsEngines {
return null;
}
public synchronized void updateLocalePrefForEngine(String name, String 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 {@line Locale#getDefault()}.
*/
public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_LOCALE);
if (DBG) {
Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale +
Log.d(TAG, "updateLocalePrefForEngine(" + engineName + ", " + newLocale +
"), originally: " + prefList);
}
final String newPrefList = updateValueInCommaSeparatedList(prefList,
name, newLocale);
engineName, (newLocale != null) ? newLocale.toString() : "");
if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());

View File

@@ -0,0 +1,64 @@
package com.android.speech.tts;
import android.speech.tts.TtsEngines;
import android.test.InstrumentationTestCase;
import java.util.Locale;
public class TtsEnginesTests extends InstrumentationTestCase {
private TtsEngines mTtsHelper;
@Override
public void setUp() {
mTtsHelper = new TtsEngines(getInstrumentation().getContext());
}
public void testParseLocaleString() {
assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng-usa"));
assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng-USA"));
assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("en-US"));
assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("en_us"));
assertEquals(new Locale("en", "US"), mTtsHelper.parseLocaleString("eng_US"));
assertEquals(new Locale("en", "US", "foobar"),
mTtsHelper.parseLocaleString("eng_US-foobar"));
assertEquals(new Locale("en", "", "foobar"), mTtsHelper.parseLocaleString("eng__foobar"));
assertNull(mTtsHelper.parseLocaleString("cc_xx_barbar"));
assertNull(mTtsHelper.parseLocaleString("cc--barbar"));
assertEquals(new Locale("en"), mTtsHelper.parseLocaleString("eng"));
assertEquals(new Locale("en","US","var"), mTtsHelper.parseLocaleString("eng-USA-var"));
}
public void testToOldLocaleStringFormat() {
assertArraysEqual(new String[]{"deu", "DEU", ""},
TtsEngines.toOldLocaleStringFormat(new Locale("de", "DE")));
assertArraysEqual(new String[]{"deu", "", ""},
TtsEngines.toOldLocaleStringFormat(new Locale("de")));
assertArraysEqual(new String[]{"eng", "", ""},
TtsEngines.toOldLocaleStringFormat(new Locale("en")));
assertArraysEqual(new String[]{"eng", "USA", ""},
TtsEngines.toOldLocaleStringFormat(new Locale("foo")));
}
public void testGetLocalePrefForEngine() {
assertEquals(new Locale("en", "US"),
mTtsHelper.getLocalePrefForEngine("foo","foo:en-US"));
assertEquals(new Locale("en", "US"),
mTtsHelper.getLocalePrefForEngine("foo","foo:eng-usa"));
assertEquals(new Locale("en", "US"),
mTtsHelper.getLocalePrefForEngine("foo","foo:eng_USA"));
assertEquals(new Locale("de", "DE"),
mTtsHelper.getLocalePrefForEngine("foo","foo:deu-deu"));
assertEquals(Locale.getDefault(),
mTtsHelper.getLocalePrefForEngine("foo","foo:,bar:xx"));
assertEquals(Locale.getDefault(),
mTtsHelper.getLocalePrefForEngine("other","foo:,bar:xx"));
}
private void assertArraysEqual(String[] expected, String[] actual) {
assertEquals("array length", expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
assertEquals("index " + i, expected[i], actual[i]);
}
}
}