1d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath/*
2d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Copyright (C) 2011 The Android Open Source Project
3d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
4d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * use this file except in compliance with the License. You may obtain a copy of
6d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * the License at
7d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
8d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * http://www.apache.org/licenses/LICENSE-2.0
9d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
10d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Unless required by applicable law or agreed to in writing, software
11d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * License for the specific language governing permissions and limitations under
14d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * the License.
15d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */
16d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathpackage android.speech.tts;
17d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
184d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport org.xmlpull.v1.XmlPullParserException;
194d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
20e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamathimport android.content.ContentResolver;
21d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.Context;
22d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.Intent;
23d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ApplicationInfo;
24d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.PackageManager;
254d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.pm.PackageManager.NameNotFoundException;
26d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ResolveInfo;
27d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ServiceInfo;
284d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.Resources;
294d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.TypedArray;
304d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.XmlResourceParser;
31e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamathimport static android.provider.Settings.Secure.getString;
32e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
33d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.provider.Settings;
34d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.speech.tts.TextToSpeech.Engine;
35d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.speech.tts.TextToSpeech.EngineInfo;
36d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.text.TextUtils;
374d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.util.AttributeSet;
384d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.util.Log;
394d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.util.Xml;
40d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
414d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport java.io.IOException;
42d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport java.util.ArrayList;
43d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport java.util.Collections;
44d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport java.util.Comparator;
45d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport java.util.List;
46e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamathimport java.util.Locale;
47d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
48d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath/**
49d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Support class for querying the list of available engines
50d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * on the device and deciding which one to use etc.
51d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
52d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Comments in this class the use the shorthand "system engines" for engines that
53d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * are a part of the system image.
54d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
55d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * @hide
56d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */
57d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathpublic class TtsEngines {
584d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private static final String TAG = "TtsEngines";
59e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static final boolean DBG = false;
60e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
61e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static final String LOCALE_DELIMITER = "-";
624d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
63d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private final Context mContext;
64d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
65d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public TtsEngines(Context ctx) {
66d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        mContext = ctx;
67d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
68d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
69d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
700e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     * @return the default TTS engine. If the user has set a default, and the engine
710e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     *         is available on the device, the default is returned. Otherwise,
720e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     *         the highest ranked engine is returned as per {@link EngineInfoComparator}.
73d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
74d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public String getDefaultEngine() {
75e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String engine = getString(mContext.getContentResolver(),
76d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                Settings.Secure.TTS_DEFAULT_SYNTH);
770e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        return isEngineInstalled(engine) ? engine : getHighestRankedEngineName();
78d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
79d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
80d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
81d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * @return the package name of the highest ranked system engine, {@code null}
82d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     *         if no TTS engines were present in the system image.
83d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
84d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public String getHighestRankedEngineName() {
85d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        final List<EngineInfo> engines = getEngines();
86d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
87d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (engines.size() > 0 && engines.get(0).system) {
88d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return engines.get(0).name;
89d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
90d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
91d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
92d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
93d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
94d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
95d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * Returns the engine info for a given engine name. Note that engines are
96d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * identified by their package name.
97d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
98d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public EngineInfo getEngineInfo(String packageName) {
99d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        PackageManager pm = mContext.getPackageManager();
100d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
101d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        intent.setPackage(packageName);
102d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
103d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                PackageManager.MATCH_DEFAULT_ONLY);
104d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // Note that the current API allows only one engine per
105d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // package name. Since the "engine name" is the same as
106d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // the package name.
107d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (resolveInfos != null && resolveInfos.size() == 1) {
108d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return getEngineInfo(resolveInfos.get(0), pm);
109d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
110d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
111d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
112d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
113d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
114d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
115d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * Gets a list of all installed TTS engines.
116d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     *
117d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * @return A list of engine info objects. The list can be empty, but never {@code null}.
118d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
119d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public List<EngineInfo> getEngines() {
120d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        PackageManager pm = mContext.getPackageManager();
121d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
122d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<ResolveInfo> resolveInfos =
123d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
124d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (resolveInfos == null) return Collections.emptyList();
125d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
126d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
127d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
128d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        for (ResolveInfo resolveInfo : resolveInfos) {
129d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            EngineInfo engine = getEngineInfo(resolveInfo, pm);
130d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            if (engine != null) {
131d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                engines.add(engine);
132d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            }
133d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
134d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Collections.sort(engines, EngineInfoComparator.INSTANCE);
135d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
136d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return engines;
137d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
138d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
139d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private boolean isSystemEngine(ServiceInfo info) {
140d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        final ApplicationInfo appInfo = info.applicationInfo;
141d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
142d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
143d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
1440e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath    /**
145c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath     * @return true if a given engine is installed on the system.
1460e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     */
147c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath    public boolean isEngineInstalled(String engine) {
1480e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        if (engine == null) {
1490e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath            return false;
1500e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        }
1510e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath
152c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath        return getEngineInfo(engine) != null;
1530e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath    }
1540e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath
1554d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    /**
1564d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * @return an intent that can launch the settings activity for a given tts engine.
1574d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     */
1584d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    public Intent getSettingsIntent(String engine) {
1594d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        PackageManager pm = mContext.getPackageManager();
1604d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
1614d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        intent.setPackage(engine);
1624d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
1634d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA);
1644d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // Note that the current API allows only one engine per
1654d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // package name. Since the "engine name" is the same as
1664d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // the package name.
1674d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        if (resolveInfos != null && resolveInfos.size() == 1) {
1684d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            ServiceInfo service = resolveInfos.get(0).serviceInfo;
1694d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (service != null) {
1704d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                final String settings = settingsActivityFromServiceInfo(service, pm);
1714d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                if (settings != null) {
1724d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    Intent i = new Intent();
1734d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    i.setClassName(engine, settings);
1744d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    return i;
1754d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                }
1764d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
1774d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        }
1784d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1794d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        return null;
1804d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    }
1814d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1824d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    /**
1834d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * The name of the XML tag that text to speech engines must use to
1844d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * declare their meta data.
1854d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     *
186e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * {@link com.android.internal.R.styleable#TextToSpeechEngine}
1874d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     */
1884d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private static final String XML_TAG_NAME = "tts-engine";
1894d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1904d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private String settingsActivityFromServiceInfo(ServiceInfo si, PackageManager pm) {
1914d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        XmlResourceParser parser = null;
1924d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        try {
1934d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            parser = si.loadXmlMetaData(pm, TextToSpeech.Engine.SERVICE_META_DATA);
1944d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (parser == null) {
1954d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                Log.w(TAG, "No meta-data found for :" + si);
1964d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                return null;
1974d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
1984d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1994d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            final Resources res = pm.getResourcesForApplication(si.applicationInfo);
2004d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2014d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            int type;
2024d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
2034d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                if (type == XmlResourceParser.START_TAG) {
2044d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    if (!XML_TAG_NAME.equals(parser.getName())) {
2054d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                        Log.w(TAG, "Package " + si + " uses unknown tag :"
2064d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                                + parser.getName());
2074d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                        return null;
2084d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    }
2094d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2104d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final AttributeSet attrs = Xml.asAttributeSet(parser);
2114d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final TypedArray array = res.obtainAttributes(attrs,
2124d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                            com.android.internal.R.styleable.TextToSpeechEngine);
2134d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final String settings = array.getString(
2144d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                            com.android.internal.R.styleable.TextToSpeechEngine_settingsActivity);
2154d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    array.recycle();
2164d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2174d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    return settings;
2184d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                }
2194d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
2204d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2214d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2224d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (NameNotFoundException e) {
2234d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Could not load resources for : " + si);
2244d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2254d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (XmlPullParserException e) {
2264d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
2274d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2284d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (IOException e) {
2294d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
2304d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2314d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } finally {
2324d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (parser != null) {
2334d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                parser.close();
2344d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
2354d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        }
2364d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    }
2374d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
238d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private EngineInfo getEngineInfo(ResolveInfo resolve, PackageManager pm) {
239d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        ServiceInfo service = resolve.serviceInfo;
240d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (service != null) {
241d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            EngineInfo engine = new EngineInfo();
242d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            // Using just the package name isn't great, since it disallows having
243d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            // multiple engines in the same package, but that's what the existing API does.
244d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.name = service.packageName;
245d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            CharSequence label = service.loadLabel(pm);
246d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
247d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.icon = service.getIconResource();
248d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.priority = resolve.priority;
249d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.system = isSystemEngine(service);
250d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return engine;
251d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
252d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
253d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
254d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
255d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
256d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private static class EngineInfoComparator implements Comparator<EngineInfo> {
257d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        private EngineInfoComparator() { }
258d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
259d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        static EngineInfoComparator INSTANCE = new EngineInfoComparator();
260d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
261d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        /**
262d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * Engines that are a part of the system image are always lesser
263d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * than those that are not. Within system engines / non system engines
264d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * the engines are sorted in order of their declared priority.
265d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         */
266d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        @Override
267d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        public int compare(EngineInfo lhs, EngineInfo rhs) {
268d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            if (lhs.system && !rhs.system) {
269d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return -1;
270d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            } else if (rhs.system && !lhs.system) {
271d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return 1;
272d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            } else {
273d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // Either both system engines, or both non system
274d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // engines.
275d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                //
276d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // Note, this isn't a typo. Higher priority numbers imply
277d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // higher priority, but are "lower" in the sort order.
278d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return rhs.priority - lhs.priority;
279d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            }
280d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
281d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
282d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
283e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
284e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Returns the locale string for a given TTS engine. Attempts to read the
285e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
286e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
287e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * both these values are empty, the default phone locale is returned.
288e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *
289e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @param engineName the engine to return the locale for.
290e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @return the locale string preference for this engine. Will be non null
291e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         and non empty.
292e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
293e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public String getLocalePrefForEngine(String engineName) {
294e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String locale = parseEnginePrefFromList(
295e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
296e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                engineName);
297e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
298e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(locale)) {
299e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // The new style setting is unset, attempt to return the old style setting.
300e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            locale = getV1Locale();
301e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
302e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
303e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + locale);
304e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
305e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return locale;
306e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
307e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
308e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
309e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}.
310e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Varies from {@link String#split} in that it will always return an array
311e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * of length 3 with non null values.
312e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
313e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public static String[] parseLocalePref(String pref) {
314e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] returnVal = new String[] { "", "", ""};
315e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(pref)) {
316e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String[] split = pref.split(LOCALE_DELIMITER);
317e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            System.arraycopy(split, 0, returnVal, 0, split.length);
318e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
319e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
320e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] +
321e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                "," + returnVal[2] +")");
322e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
323e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return returnVal;
324e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
325e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
326e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
327e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @return the old style locale string constructed from
328e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_LANG},
329e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_COUNTRY} and
330e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_VARIANT}. If no such locale is set,
331e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         then return the default phone locale.
332e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
333e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String getV1Locale() {
334e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final ContentResolver cr = mContext.getContentResolver();
335e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
336e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String lang = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_LANG);
337e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String country = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_COUNTRY);
338e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String variant = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_VARIANT);
339e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
340e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(lang)) {
341e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return getDefaultLocale();
342e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
343e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
344e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String v1Locale = lang;
345e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(country)) {
346e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            v1Locale += LOCALE_DELIMITER + country;
34739268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        } else {
34839268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            return v1Locale;
349e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
35039268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath
351e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(variant)) {
352e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            v1Locale += LOCALE_DELIMITER + variant;
353e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
354e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
355e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return v1Locale;
356e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
357e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
358e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String getDefaultLocale() {
359e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final Locale locale = Locale.getDefault();
360e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
36139268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        // Note that the default locale might have an empty variant
36239268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        // or language, and we take care that the construction is
36339268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        // the same as {@link #getV1Locale} i.e no trailing delimiters
36439268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        // or spaces.
36539268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        String defaultLocale = locale.getISO3Language();
36639268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        if (TextUtils.isEmpty(defaultLocale)) {
36739268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            Log.w(TAG, "Default locale is empty.");
36839268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            return "";
36939268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        }
37039268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath
37139268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        if (!TextUtils.isEmpty(locale.getISO3Country())) {
37239268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
37339268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        } else {
37439268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            // Do not allow locales of the form lang--variant with
37539268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            // an empty country.
37639268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            return defaultLocale;
37739268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        }
37839268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        if (!TextUtils.isEmpty(locale.getVariant())) {
37939268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            defaultLocale += LOCALE_DELIMITER + locale.getVariant();
38039268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        }
38139268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath
38239268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        return defaultLocale;
383e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
384e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
385e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
386e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Parses a comma separated list of engine locale preferences. The list is of the
387e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * form {@code "engine_name_1:locale_1,engine_name_2:locale2"} and so on and
388e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * so forth. Returns null if the list is empty, malformed or if there is no engine
389e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * specific preference in the list.
390e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
391e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static String parseEnginePrefFromList(String prefValue, String engineName) {
392e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(prefValue)) {
393e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return null;
394e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
395e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
396e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] prefValues = prefValue.split(",");
397e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
398e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        for (String value : prefValues) {
399e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            final int delimiter = value.indexOf(':');
400e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (delimiter > 0) {
401e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                if (engineName.equals(value.substring(0, delimiter))) {
402e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    return value.substring(delimiter + 1);
403e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                }
404e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
405e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
406e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
407e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return null;
408e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
409e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
410e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public synchronized void updateLocalePrefForEngine(String name, String newLocale) {
411e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
412e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                Settings.Secure.TTS_DEFAULT_LOCALE);
413e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) {
414e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale +
415e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    "), originally: " + prefList);
416e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
417e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
418e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String newPrefList = updateValueInCommaSeparatedList(prefList,
419e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                name, newLocale);
420e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
421e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());
422e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
423e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        Settings.Secure.putString(mContext.getContentResolver(),
424e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                Settings.Secure.TTS_DEFAULT_LOCALE, newPrefList.toString());
425e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
426e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
427e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
428e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Updates the value for a given key in a comma separated list of key value pairs,
429e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * each of which are delimited by a colon. If no value exists for the given key,
430e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * the kay value pair are appended to the end of the list.
431e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
432e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String updateValueInCommaSeparatedList(String list, String key,
433e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String newValue) {
434e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        StringBuilder newPrefList = new StringBuilder();
435e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(list)) {
436e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // If empty, create a new list with a single entry.
437e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            newPrefList.append(key).append(':').append(newValue);
438e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        } else {
439e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String[] prefValues = list.split(",");
440e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // Whether this is the first iteration in the loop.
441e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            boolean first = true;
442e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // Whether we found the given key.
443e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            boolean found = false;
444e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            for (String value : prefValues) {
445e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                final int delimiter = value.indexOf(':');
446e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                if (delimiter > 0) {
447e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    if (key.equals(value.substring(0, delimiter))) {
448e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        if (first) {
449e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            first = false;
450e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        } else {
451e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            newPrefList.append(',');
452e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        }
453e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        found = true;
454e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        newPrefList.append(key).append(':').append(newValue);
455e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    } else {
456e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        if (first) {
457e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            first = false;
458e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        } else {
459e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            newPrefList.append(',');
460e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        }
461e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        // Copy across the entire key + value as is.
462e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        newPrefList.append(value);
463e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    }
464e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                }
465e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
466e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
467e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!found) {
468e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                // Not found, but the rest of the keys would have been copied
469e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                // over already, so just append it to the end.
470e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                newPrefList.append(',');
471e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                newPrefList.append(key).append(':').append(newValue);
472e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
473e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
474e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
475e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return newPrefList.toString();
476e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
477d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath}
478