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;
47ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniakimport java.util.MissingResourceException;
48d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
49d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath/**
50d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Support class for querying the list of available engines
51d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * on the device and deciding which one to use etc.
52d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
53d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Comments in this class the use the shorthand "system engines" for engines that
54d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * are a part of the system image.
55d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath *
56d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * @hide
57d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */
58d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathpublic class TtsEngines {
594d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private static final String TAG = "TtsEngines";
60e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static final boolean DBG = false;
61e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
62e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static final String LOCALE_DELIMITER = "-";
634d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
64d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private final Context mContext;
65d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
66d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public TtsEngines(Context ctx) {
67d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        mContext = ctx;
68d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
69d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
70d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
710e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     * @return the default TTS engine. If the user has set a default, and the engine
720e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     *         is available on the device, the default is returned. Otherwise,
730e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     *         the highest ranked engine is returned as per {@link EngineInfoComparator}.
74d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
75d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public String getDefaultEngine() {
76e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String engine = getString(mContext.getContentResolver(),
77d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                Settings.Secure.TTS_DEFAULT_SYNTH);
780e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        return isEngineInstalled(engine) ? engine : getHighestRankedEngineName();
79d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
80d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
81d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
82d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * @return the package name of the highest ranked system engine, {@code null}
83d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     *         if no TTS engines were present in the system image.
84d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
85d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public String getHighestRankedEngineName() {
86d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        final List<EngineInfo> engines = getEngines();
87d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
88d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (engines.size() > 0 && engines.get(0).system) {
89d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return engines.get(0).name;
90d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
91d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
92d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
93d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
94d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
95d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
96d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * Returns the engine info for a given engine name. Note that engines are
97d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * identified by their package name.
98d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
99d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public EngineInfo getEngineInfo(String packageName) {
100d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        PackageManager pm = mContext.getPackageManager();
101d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
102d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        intent.setPackage(packageName);
103d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
104d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                PackageManager.MATCH_DEFAULT_ONLY);
105d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // Note that the current API allows only one engine per
106d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // package name. Since the "engine name" is the same as
107d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        // the package name.
108d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (resolveInfos != null && resolveInfos.size() == 1) {
109d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return getEngineInfo(resolveInfos.get(0), pm);
110d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
111d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
112d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
113d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
114d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
115d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    /**
116d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * Gets a list of all installed TTS engines.
117d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     *
118d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     * @return A list of engine info objects. The list can be empty, but never {@code null}.
119d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath     */
120d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    public List<EngineInfo> getEngines() {
121d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        PackageManager pm = mContext.getPackageManager();
122d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
123d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<ResolveInfo> resolveInfos =
124d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
125d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (resolveInfos == null) return Collections.emptyList();
126d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
127d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
128d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
129d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        for (ResolveInfo resolveInfo : resolveInfos) {
130d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            EngineInfo engine = getEngineInfo(resolveInfo, pm);
131d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            if (engine != null) {
132d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                engines.add(engine);
133d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            }
134d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
135d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        Collections.sort(engines, EngineInfoComparator.INSTANCE);
136d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
137d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return engines;
138d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
139d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
140d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private boolean isSystemEngine(ServiceInfo info) {
141d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        final ApplicationInfo appInfo = info.applicationInfo;
142d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
143d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
144d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
1450e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath    /**
146c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath     * @return true if a given engine is installed on the system.
1470e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath     */
148c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath    public boolean isEngineInstalled(String engine) {
1490e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        if (engine == null) {
1500e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath            return false;
1510e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath        }
1520e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath
153c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath        return getEngineInfo(engine) != null;
1540e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath    }
1550e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath
1564d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    /**
1574d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * @return an intent that can launch the settings activity for a given tts engine.
1584d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     */
1594d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    public Intent getSettingsIntent(String engine) {
1604d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        PackageManager pm = mContext.getPackageManager();
1614d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
1624d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        intent.setPackage(engine);
1634d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent,
1644d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA);
1654d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // Note that the current API allows only one engine per
1664d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // package name. Since the "engine name" is the same as
1674d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        // the package name.
1684d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        if (resolveInfos != null && resolveInfos.size() == 1) {
1694d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            ServiceInfo service = resolveInfos.get(0).serviceInfo;
1704d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (service != null) {
1714d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                final String settings = settingsActivityFromServiceInfo(service, pm);
1724d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                if (settings != null) {
1734d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    Intent i = new Intent();
1744d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    i.setClassName(engine, settings);
1754d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    return i;
1764d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                }
1774d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
1784d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        }
1794d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1804d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        return null;
1814d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    }
1824d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1834d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    /**
1844d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * The name of the XML tag that text to speech engines must use to
1854d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     * declare their meta data.
1864d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     *
187e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * {@link com.android.internal.R.styleable#TextToSpeechEngine}
1884d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath     */
1894d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private static final String XML_TAG_NAME = "tts-engine";
1904d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
1914d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    private String settingsActivityFromServiceInfo(ServiceInfo si, PackageManager pm) {
1924d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        XmlResourceParser parser = null;
1934d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        try {
1944d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            parser = si.loadXmlMetaData(pm, TextToSpeech.Engine.SERVICE_META_DATA);
1954d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (parser == null) {
1964d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                Log.w(TAG, "No meta-data found for :" + si);
1974d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                return null;
1984d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
1994d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2004d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            final Resources res = pm.getResourcesForApplication(si.applicationInfo);
2014d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2024d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            int type;
2034d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
2044d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                if (type == XmlResourceParser.START_TAG) {
2054d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    if (!XML_TAG_NAME.equals(parser.getName())) {
2064d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                        Log.w(TAG, "Package " + si + " uses unknown tag :"
2074d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                                + parser.getName());
2084d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                        return null;
2094d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    }
2104d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2114d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final AttributeSet attrs = Xml.asAttributeSet(parser);
2124d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final TypedArray array = res.obtainAttributes(attrs,
2134d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                            com.android.internal.R.styleable.TextToSpeechEngine);
2144d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    final String settings = array.getString(
2154d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                            com.android.internal.R.styleable.TextToSpeechEngine_settingsActivity);
2164d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    array.recycle();
2174d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2184d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                    return settings;
2194d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                }
2204d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
2214d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
2224d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2234d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (NameNotFoundException e) {
2244d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Could not load resources for : " + si);
2254d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2264d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (XmlPullParserException e) {
2274d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
2284d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2294d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } catch (IOException e) {
2304d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            Log.w(TAG, "Error parsing metadata for " + si + ":" + e);
2314d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            return null;
2324d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        } finally {
2334d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            if (parser != null) {
2344d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath                parser.close();
2354d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath            }
2364d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath        }
2374d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath    }
2384d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath
239d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private EngineInfo getEngineInfo(ResolveInfo resolve, PackageManager pm) {
240d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        ServiceInfo service = resolve.serviceInfo;
241d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        if (service != null) {
242d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            EngineInfo engine = new EngineInfo();
243d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            // Using just the package name isn't great, since it disallows having
244d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            // multiple engines in the same package, but that's what the existing API does.
245d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.name = service.packageName;
246d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            CharSequence label = service.loadLabel(pm);
247d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
248d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.icon = service.getIconResource();
249d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.priority = resolve.priority;
250d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            engine.system = isSystemEngine(service);
251d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            return engine;
252d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
253d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
254d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        return null;
255d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
256d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
257d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    private static class EngineInfoComparator implements Comparator<EngineInfo> {
258d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        private EngineInfoComparator() { }
259d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
260d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        static EngineInfoComparator INSTANCE = new EngineInfoComparator();
261d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
262d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        /**
263d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * Engines that are a part of the system image are always lesser
264d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * than those that are not. Within system engines / non system engines
265d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         * the engines are sorted in order of their declared priority.
266d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath         */
267d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        @Override
268d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        public int compare(EngineInfo lhs, EngineInfo rhs) {
269d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            if (lhs.system && !rhs.system) {
270d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return -1;
271d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            } else if (rhs.system && !lhs.system) {
272d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return 1;
273d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            } else {
274d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // Either both system engines, or both non system
275d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // engines.
276d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                //
277d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // Note, this isn't a typo. Higher priority numbers imply
278d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                // higher priority, but are "lower" in the sort order.
279d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath                return rhs.priority - lhs.priority;
280d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath            }
281d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath        }
282d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath    }
283d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath
284e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
285e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Returns the locale string for a given TTS engine. Attempts to read the
286e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the
287e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * old style value from {@link Settings.Secure#TTS_DEFAULT_LANG} is read. If
288e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * both these values are empty, the default phone locale is returned.
289e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *
290e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @param engineName the engine to return the locale for.
291e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @return the locale string preference for this engine. Will be non null
292e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         and non empty.
293e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
294e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public String getLocalePrefForEngine(String engineName) {
295e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String locale = parseEnginePrefFromList(
296e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),
297e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                engineName);
298e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
299e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(locale)) {
300e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // The new style setting is unset, attempt to return the old style setting.
301e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            locale = getV1Locale();
302e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
303e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
304e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + locale);
305e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
306e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return locale;
307e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
308e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
309e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
310e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Parses a locale preference value delimited by {@link #LOCALE_DELIMITER}.
311e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Varies from {@link String#split} in that it will always return an array
312e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * of length 3 with non null values.
313e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
314e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public static String[] parseLocalePref(String pref) {
315e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] returnVal = new String[] { "", "", ""};
316e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(pref)) {
317e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String[] split = pref.split(LOCALE_DELIMITER);
318e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            System.arraycopy(split, 0, returnVal, 0, split.length);
319e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
320e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
321e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "parseLocalePref(" + returnVal[0] + "," + returnVal[1] +
322e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                "," + returnVal[2] +")");
323e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
324e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return returnVal;
325e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
326e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
327e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
328e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * @return the old style locale string constructed from
329e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_LANG},
330e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_COUNTRY} and
331e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         {@link Settings.Secure#TTS_DEFAULT_VARIANT}. If no such locale is set,
332e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     *         then return the default phone locale.
333e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
334e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String getV1Locale() {
335e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final ContentResolver cr = mContext.getContentResolver();
336e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
337e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String lang = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_LANG);
338e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String country = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_COUNTRY);
339e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String variant = Settings.Secure.getString(cr, Settings.Secure.TTS_DEFAULT_VARIANT);
340e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
341e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(lang)) {
342e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return getDefaultLocale();
343e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
344e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
345e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String v1Locale = lang;
346e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(country)) {
347e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            v1Locale += LOCALE_DELIMITER + country;
34839268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        } else {
34939268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            return v1Locale;
350e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
35139268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath
352e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (!TextUtils.isEmpty(variant)) {
353e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            v1Locale += LOCALE_DELIMITER + variant;
354e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
355e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
356e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return v1Locale;
357e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
358e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
35940d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak    /**
36040d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     * Return the default device locale in form of 3 letter codes delimited by
36140d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     * {@link #LOCALE_DELIMITER}:
36240d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     * <ul>
36340d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     *   <li> "ISO 639-2/T language code" if locale have no country entry</li>
36440d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code "
36540d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     *     if locale have no variant entry</li>
36640d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     *   <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code
36740d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     *     {@link #LOCALE_DELIMITER} variant" if locale have variant entry</li>
36840d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     * </ul>
36940d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak     */
37040d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak    public String getDefaultLocale() {
371e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final Locale locale = Locale.getDefault();
372e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
373ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak        try {
374ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // Note that the default locale might have an empty variant
375ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // or language, and we take care that the construction is
376ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // the same as {@link #getV1Locale} i.e no trailing delimiters
377ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // or spaces.
378ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            String defaultLocale = locale.getISO3Language();
379ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            if (TextUtils.isEmpty(defaultLocale)) {
380ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                Log.w(TAG, "Default locale is empty.");
381ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                return "";
382ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            }
383ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak
384ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            if (!TextUtils.isEmpty(locale.getISO3Country())) {
385ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                defaultLocale += LOCALE_DELIMITER + locale.getISO3Country();
386ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            } else {
387ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                // Do not allow locales of the form lang--variant with
388ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                // an empty country.
389ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                return defaultLocale;
390ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            }
391ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            if (!TextUtils.isEmpty(locale.getVariant())) {
392ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak                defaultLocale += LOCALE_DELIMITER + locale.getVariant();
393ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            }
39439268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath
39539268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath            return defaultLocale;
396ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak        } catch (MissingResourceException e) {
397ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the
398ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US).
399ca4f1fc57c86fb7e6094ba4117a027fffd79d227Przemyslaw Szczepaniak            return "eng-usa";
40039268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath        }
401e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
402e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
403e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
404e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Parses a comma separated list of engine locale preferences. The list is of the
405e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * form {@code "engine_name_1:locale_1,engine_name_2:locale2"} and so on and
406e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * so forth. Returns null if the list is empty, malformed or if there is no engine
407e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * specific preference in the list.
408e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
409e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private static String parseEnginePrefFromList(String prefValue, String engineName) {
410e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(prefValue)) {
411e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            return null;
412e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
413e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
414e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        String[] prefValues = prefValue.split(",");
415e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
416e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        for (String value : prefValues) {
417e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            final int delimiter = value.indexOf(':');
418e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (delimiter > 0) {
419e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                if (engineName.equals(value.substring(0, delimiter))) {
420e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    return value.substring(delimiter + 1);
421e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                }
422e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
423e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
424e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
425e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return null;
426e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
427e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
428e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    public synchronized void updateLocalePrefForEngine(String name, String newLocale) {
429e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
430e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                Settings.Secure.TTS_DEFAULT_LOCALE);
431e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) {
432e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            Log.d(TAG, "updateLocalePrefForEngine(" + name + ", " + newLocale +
433e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    "), originally: " + prefList);
434e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
435e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
436e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        final String newPrefList = updateValueInCommaSeparatedList(prefList,
437e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                name, newLocale);
438e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
439e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString());
440e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
441e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        Settings.Secure.putString(mContext.getContentResolver(),
442e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                Settings.Secure.TTS_DEFAULT_LOCALE, newPrefList.toString());
443e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
444e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
445e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    /**
446e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * Updates the value for a given key in a comma separated list of key value pairs,
447e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * each of which are delimited by a colon. If no value exists for the given key,
448e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     * the kay value pair are appended to the end of the list.
449e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath     */
450e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    private String updateValueInCommaSeparatedList(String list, String key,
451e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String newValue) {
452e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        StringBuilder newPrefList = new StringBuilder();
453e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        if (TextUtils.isEmpty(list)) {
454e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // If empty, create a new list with a single entry.
455e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            newPrefList.append(key).append(':').append(newValue);
456e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        } else {
457e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            String[] prefValues = list.split(",");
458e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // Whether this is the first iteration in the loop.
459e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            boolean first = true;
460e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            // Whether we found the given key.
461e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            boolean found = false;
462e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            for (String value : prefValues) {
463e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                final int delimiter = value.indexOf(':');
464e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                if (delimiter > 0) {
465e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    if (key.equals(value.substring(0, delimiter))) {
466e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        if (first) {
467e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            first = false;
468e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        } else {
469e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            newPrefList.append(',');
470e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        }
471e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        found = true;
472e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        newPrefList.append(key).append(':').append(newValue);
473e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    } else {
474e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        if (first) {
475e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            first = false;
476e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        } else {
477e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                            newPrefList.append(',');
478e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        }
479e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        // Copy across the entire key + value as is.
480e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                        newPrefList.append(value);
481e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                    }
482e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                }
483e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
484e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
485e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            if (!found) {
486e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                // Not found, but the rest of the keys would have been copied
487e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                // over already, so just append it to the end.
488e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                newPrefList.append(',');
489e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath                newPrefList.append(key).append(':').append(newValue);
490e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath            }
491e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        }
492e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath
493e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath        return newPrefList.toString();
494e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath    }
495d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath}
496