Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a7785806 authored by Narayan Kamath's avatar Narayan Kamath Committed by Android (Google) Code Review
Browse files

Merge "Framework changes to support new TTS settings features."

parents 9c8caeff e5b8c4df
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -2814,6 +2814,16 @@ public final class Settings {
         */
        public static final String TTS_DEFAULT_VARIANT = "tts_default_variant";

        /**
         * Stores the default tts locales on a per engine basis. Stored as
         * a comma seperated list of values, each value being of the form
         * {@code engine_name:locale} for example,
         * {@code com.foo.ttsengine:eng-USA,com.bar.ttsengine:esp-ESP}.
         *
         * @hide
         */
        public static final String TTS_DEFAULT_LOCALE = "tts_default_locale";

        /**
         * Space delimited list of plugin packages that are enabled.
         */
+14 −22
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import java.util.Locale;
 *
 * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
 * any. Any pending data from the current synthesis will be discarded.
 *
 */
// TODO: Add a link to the sample TTS engine once it's done.
public abstract class TextToSpeechService extends Service {
@@ -80,6 +81,7 @@ public abstract class TextToSpeechService extends Service {
    // associated with this TTS engine. Will handle all requests except synthesis
    // to file requests, which occur on the synthesis thread.
    private AudioPlaybackHandler mAudioPlaybackHandler;
    private TtsEngines mEngineHelper;

    private CallbackMap mCallbacks;
    private String mPackageName;
@@ -96,12 +98,15 @@ public abstract class TextToSpeechService extends Service {
        mAudioPlaybackHandler = new AudioPlaybackHandler();
        mAudioPlaybackHandler.start();

        mEngineHelper = new TtsEngines(this);

        mCallbacks = new CallbackMap();

        mPackageName = getApplicationInfo().packageName;

        String[] defaultLocale = getSettingsLocale();
        // Load default language
        onLoadLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
        onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
    }

    @Override
@@ -195,30 +200,15 @@ public abstract class TextToSpeechService extends Service {
        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
    }

    private String getDefaultLanguage() {
        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_LANG,
                Locale.getDefault().getISO3Language());
    }

    private String getDefaultCountry() {
        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_COUNTRY,
                Locale.getDefault().getISO3Country());
    }

    private String getDefaultVariant() {
        return getSecureSettingString(Settings.Secure.TTS_DEFAULT_VARIANT,
                Locale.getDefault().getVariant());
    private String[] getSettingsLocale() {
        final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
        return TtsEngines.parseLocalePref(locale);
    }

    private int getSecureSettingInt(String name, int defaultValue) {
        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
    }

    private String getSecureSettingString(String name, String defaultValue) {
        String value = Settings.Secure.getString(getContentResolver(), name);
        return value != null ? value : defaultValue;
    }

    /**
     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
     */
@@ -458,6 +448,7 @@ public abstract class TextToSpeechService extends Service {
    class SynthesisSpeechItem extends SpeechItem {
        private final String mText;
        private final SynthesisRequest mSynthesisRequest;
        private final String[] mDefaultLocale;
        // Non null after synthesis has started, and all accesses
        // guarded by 'this'.
        private AbstractSynthesisCallback mSynthesisCallback;
@@ -467,6 +458,7 @@ public abstract class TextToSpeechService extends Service {
            super(callingApp, params);
            mText = text;
            mSynthesisRequest = new SynthesisRequest(mText, mParams);
            mDefaultLocale = getSettingsLocale();
            setRequestParams(mSynthesisRequest);
            mEventLogger = new EventLogger(mSynthesisRequest, getCallingApp(), mPackageName);
        }
@@ -523,7 +515,7 @@ public abstract class TextToSpeechService extends Service {
        }

        public String getLanguage() {
            return getStringParam(Engine.KEY_PARAM_LANGUAGE, getDefaultLanguage());
            return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
        }

        private boolean hasLanguage() {
@@ -531,12 +523,12 @@ public abstract class TextToSpeechService extends Service {
        }

        private String getCountry() {
            if (!hasLanguage()) return getDefaultCountry();
            if (!hasLanguage()) return mDefaultLocale[1];
            return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
        }

        private String getVariant() {
            if (!hasLanguage()) return getDefaultVariant();
            if (!hasLanguage()) return mDefaultLocale[2];
            return getStringParam(Engine.KEY_PARAM_VARIANT, "");
        }

+180 −8
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package android.speech.tts;

import org.xmlpull.v1.XmlPullParserException;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -27,6 +28,8 @@ 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;
import android.speech.tts.TextToSpeech.Engine;
import android.speech.tts.TextToSpeech.EngineInfo;
@@ -40,6 +43,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
 * Support class for querying the list of available engines
@@ -52,6 +56,9 @@ import java.util.List;
 */
public class TtsEngines {
    private static final String TAG = "TtsEngines";
    private static final boolean DBG = false;

    private static final String LOCALE_DELIMITER = "-";

    private final Context mContext;

@@ -65,7 +72,7 @@ public class TtsEngines {
     *         the highest ranked engine is returned as per {@link EngineInfoComparator}.
     */
    public String getDefaultEngine() {
        String engine = Settings.Secure.getString(mContext.getContentResolver(),
        String engine = getString(mContext.getContentResolver(),
                Settings.Secure.TTS_DEFAULT_SYNTH);
        return isEngineInstalled(engine) ? engine : getHighestRankedEngineName();
    }
@@ -129,12 +136,6 @@ public class TtsEngines {
        return engines;
    }

    // TODO: Used only by the settings app. Remove once
    // the settings UI change has been finalized.
    public boolean isEngineEnabled(String engine) {
        return isEngineInstalled(engine);
    }

    private boolean isSystemEngine(ServiceInfo info) {
        final ApplicationInfo appInfo = info.applicationInfo;
        return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
@@ -182,7 +183,7 @@ public class TtsEngines {
     * The name of the XML tag that text to speech engines must use to
     * declare their meta data.
     *
     * {@link com.android.internal.R.styleable.TextToSpeechEngine}
     * {@link com.android.internal.R.styleable#TextToSpeechEngine}
     */
    private static final String XML_TAG_NAME = "tts-engine";

@@ -279,4 +280,175 @@ public class TtsEngines {
        }
    }

    /**
     * Returns the locale string 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.
     *
     * @param engineName the engine to return the locale for.
     * @return the locale string preference for this engine. Will be non null
     *         and non empty.
     */
    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;
    }

    /**
     * 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.
     */
    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);
        }

        if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] +
                "," + returnVal[2] +")");

        return returnVal;
    }

    /**
     * @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;
        }
        if (!TextUtils.isEmpty(variant)) {
            v1Locale += LOCALE_DELIMITER + variant;
        }

        return v1Locale;
    }

    private String getDefaultLocale() {
        final Locale locale = Locale.getDefault();

        return locale.getISO3Language() + LOCALE_DELIMITER + locale.getISO3Country() +
                LOCALE_DELIMITER + locale.getVariant();
    }

    /**
     * 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.
     */
    private static String parseEnginePrefFromList(String prefValue, String engineName) {
        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 synchronized void updateLocalePrefForEngine(String name, String newLocale) {
        final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.TTS_DEFAULT_LOCALE);
        if (DBG) {
            Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale +
                    "), originally: " + prefList);
        }

        final String newPrefList = updateValueInCommaSeparatedList(prefList,
                name, newLocale);

        if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());

        Settings.Secure.putString(mContext.getContentResolver(),
                Settings.Secure.TTS_DEFAULT_LOCALE, newPrefList.toString());
    }

    /**
     * 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.
     */
    private String updateValueInCommaSeparatedList(String list, String key,
            String newValue) {
        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();
    }
}