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 20d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.Context; 21d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.Intent; 22d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ApplicationInfo; 23d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.PackageManager; 244d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.pm.PackageManager.NameNotFoundException; 25d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ResolveInfo; 26d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport android.content.pm.ServiceInfo; 274d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.Resources; 284d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.TypedArray; 294d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamathimport android.content.res.XmlResourceParser; 301b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 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; 451b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniakimport java.util.HashMap; 46d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathimport java.util.List; 47e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamathimport java.util.Locale; 481b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniakimport java.util.Map; 4958f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniakimport java.util.MissingResourceException; 50d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 51d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath/** 52d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Support class for querying the list of available engines 53d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * on the device and deciding which one to use etc. 54d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * 55d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Comments in this class the use the shorthand "system engines" for engines that 56d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * are a part of the system image. 57d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * 581b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * This class is thread-safe/ 591b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * 60d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * @hide 61d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 62d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamathpublic class TtsEngines { 634d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath private static final String TAG = "TtsEngines"; 64e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath private static final boolean DBG = false; 65e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 661b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** Locale delimiter used by the old-style 3 char locale string format (like "eng-usa") */ 671b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak private static final String LOCALE_DELIMITER_OLD = "-"; 681b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 691b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** Locale delimiter used by the new-style locale string format (Locale.toString() results, 701b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * like "en_US") */ 711b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak private static final String LOCALE_DELIMITER_NEW = "_"; 724d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 73d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath private final Context mContext; 74d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 751b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** Mapping of various language strings to the normalized Locale form */ 761b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak private static final Map<String, String> sNormalizeLanguage; 771b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 781b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** Mapping of various country strings to the normalized Locale form */ 791b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak private static final Map<String, String> sNormalizeCountry; 801b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 811b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak // Populate the sNormalize* maps 821b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak static { 831b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak HashMap<String, String> normalizeLanguage = new HashMap<String, String>(); 841b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak for (String language : Locale.getISOLanguages()) { 851b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak try { 861b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak normalizeLanguage.put(new Locale(language).getISO3Language(), language); 871b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } catch (MissingResourceException e) { 881b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak continue; 891b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 901b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 911b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak sNormalizeLanguage = Collections.unmodifiableMap(normalizeLanguage); 921b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 931b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak HashMap<String, String> normalizeCountry = new HashMap<String, String>(); 941b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak for (String country : Locale.getISOCountries()) { 951b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak try { 961b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak normalizeCountry.put(new Locale("", country).getISO3Country(), country); 971b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } catch (MissingResourceException e) { 981b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak continue; 991b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 1001b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 1011b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak sNormalizeCountry = Collections.unmodifiableMap(normalizeCountry); 1021b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 1031b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 104d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public TtsEngines(Context ctx) { 105d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath mContext = ctx; 106d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 107d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 108d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath /** 1090e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath * @return the default TTS engine. If the user has set a default, and the engine 1100e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath * is available on the device, the default is returned. Otherwise, 1110e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath * the highest ranked engine is returned as per {@link EngineInfoComparator}. 112d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 113d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public String getDefaultEngine() { 114e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath String engine = getString(mContext.getContentResolver(), 115d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath Settings.Secure.TTS_DEFAULT_SYNTH); 1160e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath return isEngineInstalled(engine) ? engine : getHighestRankedEngineName(); 117d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 118d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 119d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath /** 120d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * @return the package name of the highest ranked system engine, {@code null} 121d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * if no TTS engines were present in the system image. 122d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 123d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public String getHighestRankedEngineName() { 124d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath final List<EngineInfo> engines = getEngines(); 125d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 126d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (engines.size() > 0 && engines.get(0).system) { 127d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return engines.get(0).name; 128d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 129d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 130d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return null; 131d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 132d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 133d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath /** 134d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Returns the engine info for a given engine name. Note that engines are 135d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * identified by their package name. 136d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 137d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public EngineInfo getEngineInfo(String packageName) { 138d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath PackageManager pm = mContext.getPackageManager(); 139d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 140d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath intent.setPackage(packageName); 141d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 142d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath PackageManager.MATCH_DEFAULT_ONLY); 143d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // Note that the current API allows only one engine per 144d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // package name. Since the "engine name" is the same as 145d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // the package name. 146d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (resolveInfos != null && resolveInfos.size() == 1) { 147d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return getEngineInfo(resolveInfos.get(0), pm); 148d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 149d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 150d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return null; 151d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 152d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 153d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath /** 154d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Gets a list of all installed TTS engines. 155d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * 156d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * @return A list of engine info objects. The list can be empty, but never {@code null}. 157d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 158d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public List<EngineInfo> getEngines() { 159d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath PackageManager pm = mContext.getPackageManager(); 160d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 161d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath List<ResolveInfo> resolveInfos = 162d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY); 163d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (resolveInfos == null) return Collections.emptyList(); 164d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 165d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size()); 166d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 167d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath for (ResolveInfo resolveInfo : resolveInfos) { 168d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath EngineInfo engine = getEngineInfo(resolveInfo, pm); 169d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (engine != null) { 170d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engines.add(engine); 171d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 172d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 173d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath Collections.sort(engines, EngineInfoComparator.INSTANCE); 174d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 175d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return engines; 176d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 177d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 178d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath private boolean isSystemEngine(ServiceInfo info) { 179d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath final ApplicationInfo appInfo = info.applicationInfo; 180d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 181d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 182d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 1830e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath /** 184c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath * @return true if a given engine is installed on the system. 1850e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath */ 186c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath public boolean isEngineInstalled(String engine) { 1870e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath if (engine == null) { 1880e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath return false; 1890e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath } 1900e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath 191c3edf2a01a2cf2123a3de17ec1da11a3b6c459f0Narayan Kamath return getEngineInfo(engine) != null; 1920e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath } 1930e20fe5bab7dc3aff488d133961acfe0239f5240Narayan Kamath 1944d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath /** 1954d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath * @return an intent that can launch the settings activity for a given tts engine. 1964d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath */ 1974d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath public Intent getSettingsIntent(String engine) { 1984d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath PackageManager pm = mContext.getPackageManager(); 1994d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 2004d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath intent.setPackage(engine); 2014d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 2024d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA); 2034d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath // Note that the current API allows only one engine per 2044d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath // package name. Since the "engine name" is the same as 2054d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath // the package name. 2064d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (resolveInfos != null && resolveInfos.size() == 1) { 2074d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath ServiceInfo service = resolveInfos.get(0).serviceInfo; 2084d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (service != null) { 2094d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath final String settings = settingsActivityFromServiceInfo(service, pm); 2104d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (settings != null) { 2114d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Intent i = new Intent(); 2124d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath i.setClassName(engine, settings); 2134d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return i; 2144d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2154d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2164d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2174d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2184d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2194d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2204d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2214d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath /** 2224d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath * The name of the XML tag that text to speech engines must use to 2234d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath * declare their meta data. 2244d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath * 225e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * {@link com.android.internal.R.styleable#TextToSpeechEngine} 2264d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath */ 2274d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath private static final String XML_TAG_NAME = "tts-engine"; 2284d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2294d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath private String settingsActivityFromServiceInfo(ServiceInfo si, PackageManager pm) { 2304d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath XmlResourceParser parser = null; 2314d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath try { 2324d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath parser = si.loadXmlMetaData(pm, TextToSpeech.Engine.SERVICE_META_DATA); 2334d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (parser == null) { 2344d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Log.w(TAG, "No meta-data found for :" + si); 2354d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2364d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2374d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2384d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath final Resources res = pm.getResourcesForApplication(si.applicationInfo); 2394d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2404d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath int type; 2414d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) { 2424d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (type == XmlResourceParser.START_TAG) { 2434d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (!XML_TAG_NAME.equals(parser.getName())) { 2444d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Log.w(TAG, "Package " + si + " uses unknown tag :" 2454d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath + parser.getName()); 2464d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2474d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2484d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2494d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath final AttributeSet attrs = Xml.asAttributeSet(parser); 2504d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath final TypedArray array = res.obtainAttributes(attrs, 2514d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath com.android.internal.R.styleable.TextToSpeechEngine); 2524d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath final String settings = array.getString( 2534d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath com.android.internal.R.styleable.TextToSpeechEngine_settingsActivity); 2544d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath array.recycle(); 2554d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2564d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return settings; 2574d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2584d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2594d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 2604d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2614d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } catch (NameNotFoundException e) { 2624d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Log.w(TAG, "Could not load resources for : " + si); 2634d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2644d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } catch (XmlPullParserException e) { 2654d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Log.w(TAG, "Error parsing metadata for " + si + ":" + e); 2664d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2674d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } catch (IOException e) { 2684d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath Log.w(TAG, "Error parsing metadata for " + si + ":" + e); 2694d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath return null; 2704d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } finally { 2714d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath if (parser != null) { 2724d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath parser.close(); 2734d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2744d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2754d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath } 2764d03462b374dfc080f0c7c78d458c102a26be5c6Narayan Kamath 277d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath private EngineInfo getEngineInfo(ResolveInfo resolve, PackageManager pm) { 278d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath ServiceInfo service = resolve.serviceInfo; 279d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (service != null) { 280d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath EngineInfo engine = new EngineInfo(); 281d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // Using just the package name isn't great, since it disallows having 282d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // multiple engines in the same package, but that's what the existing API does. 283d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engine.name = service.packageName; 284d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath CharSequence label = service.loadLabel(pm); 285d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString(); 286d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engine.icon = service.getIconResource(); 287d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engine.priority = resolve.priority; 288d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath engine.system = isSystemEngine(service); 289d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return engine; 290d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 291d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 292d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return null; 293d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 294d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 295d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath private static class EngineInfoComparator implements Comparator<EngineInfo> { 296d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath private EngineInfoComparator() { } 297d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 298d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath static EngineInfoComparator INSTANCE = new EngineInfoComparator(); 299d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 300d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath /** 301d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * Engines that are a part of the system image are always lesser 302d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * than those that are not. Within system engines / non system engines 303d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath * the engines are sorted in order of their declared priority. 304d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath */ 305d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath @Override 306d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath public int compare(EngineInfo lhs, EngineInfo rhs) { 307d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath if (lhs.system && !rhs.system) { 308d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return -1; 309d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } else if (rhs.system && !lhs.system) { 310d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return 1; 311d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } else { 312d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // Either both system engines, or both non system 313d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // engines. 314d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // 315d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // Note, this isn't a typo. Higher priority numbers imply 316d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath // higher priority, but are "lower" in the sort order. 317d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath return rhs.priority - lhs.priority; 318d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 319d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 320d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath } 321d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath 322e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath /** 3231b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * Returns the default locale for a given TTS engine. Attempts to read the 324e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}, failing which the 3251b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * default phone locale is returned. 326e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * 327e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * @param engineName the engine to return the locale for. 3281b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * @return the locale preference for this engine. Will be non null. 329e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath */ 3301b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak public Locale getLocalePrefForEngine(String engineName) { 3311b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return getLocalePrefForEngine(engineName, 3321b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE)); 3331b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 3341b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 3351b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** 3361b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * Returns the default locale for a given TTS engine from given settings string. */ 3371b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak public Locale getLocalePrefForEngine(String engineName, String prefValue) { 3381b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String localeString = parseEnginePrefFromList( 3391b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak prefValue, 340e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath engineName); 341e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 3421b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (TextUtils.isEmpty(localeString)) { 343e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // The new style setting is unset, attempt to return the old style setting. 3441b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return Locale.getDefault(); 345e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 346e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 3471b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Locale result = parseLocaleString(localeString); 3481b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (result == null) { 3491b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Log.w(TAG, "Failed to parse locale " + localeString + ", returning en_US instead"); 3501b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak result = Locale.US; 3511b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 3521b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 3531b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (DBG) Log.d(TAG, "getLocalePrefForEngine(" + engineName + ")= " + result); 354e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 3551b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return result; 356e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 357e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 3581b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 359e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath /** 360de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak * True if a given TTS engine uses the default phone locale as a default locale. Attempts to 3611b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * read the value from {@link Settings.Secure#TTS_DEFAULT_LOCALE}. If 3621b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * its value is empty, this methods returns true. 363de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak * 364de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak * @param engineName the engine to return the locale for. 365de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak */ 366de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak public boolean isLocaleSetToDefaultForEngine(String engineName) { 3671b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return TextUtils.isEmpty(parseEnginePrefFromList( 368de1b5ae7a7567f03cfeecf1a62ddf429cb840474Przemyslaw Szczepaniak getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE), 3691b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak engineName)); 370e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 371e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 372e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath /** 3731b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * Parses a locale encoded as a string, and tries its best to return a valid {@link Locale} 3741b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * object, even if the input string is encoded using the old-style 3 character format e.g. 3751b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * "deu-deu". At the end, we test if the resulting locale can return ISO3 language and 3761b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * country codes ({@link Locale#getISO3Language()} and {@link Locale#getISO3Country()}), 3771b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * if it fails to do so, we return null. 378e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath */ 3791b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak public Locale parseLocaleString(String localeString) { 3801b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String language = "", country = "", variant = ""; 3811b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (!TextUtils.isEmpty(localeString)) { 3821b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String[] split = localeString.split( 3831b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak "[" + LOCALE_DELIMITER_OLD + LOCALE_DELIMITER_NEW + "]"); 3841b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak language = split[0].toLowerCase(); 3851b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (split.length == 0) { 3861b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Only" + 3871b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak " separators"); 3881b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return null; 3891b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 3901b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (split.length > 3) { 3911b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object. Too" + 3921b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak " many separators"); 3931b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return null; 3941b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 3951b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (split.length >= 2) { 3961b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak country = split[1].toUpperCase(); 3971b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 3981b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (split.length >= 3) { 3991b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak variant = split[2]; 4001b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 401e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 402e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 403e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 4041b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String normalizedLanguage = sNormalizeLanguage.get(language); 4051b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (normalizedLanguage != null) { 4061b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak language = normalizedLanguage; 407e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 40839268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath 4091b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String normalizedCountry= sNormalizeCountry.get(country); 4101b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (normalizedCountry != null) { 4111b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak country = normalizedCountry; 412e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 413e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 4141b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak if (DBG) Log.d(TAG, "parseLocalePref(" + language + "," + country + 4151b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak "," + variant +")"); 4161b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak 4171b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Locale result = new Locale(language, country, variant); 4181b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak try { 4191b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak result.getISO3Language(); 4201b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak result.getISO3Country(); 4211b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return result; 4221b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } catch(MissingResourceException e) { 4231b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Log.w(TAG, "Failed to convert " + localeString + " to a valid Locale object."); 4241b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return null; 4251b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak } 426e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 427e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 42840d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak /** 429ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * This method tries its best to return a valid {@link Locale} object from the TTS-specific 430ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * Locale input (returned by {@link TextToSpeech#getLanguage} 431ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * and {@link TextToSpeech#getDefaultLanguage}). A TTS Locale language field contains 432ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * a three-letter ISO 639-2/T code (where a proper Locale would use a two-letter ISO 639-1 433ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * code), and the country field contains a three-letter ISO 3166 country code (where a proper 434ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * Locale would use a two-letter ISO 3166-1 code). 435ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * 436ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * This method tries to convert three-letter language and country codes into their two-letter 437ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak * equivalents. If it fails to do so, it keeps the value from the TTS locale. 438ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak */ 439ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak public static Locale normalizeTTSLocale(Locale ttsLocale) { 440ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak String language = ttsLocale.getLanguage(); 441ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak if (!TextUtils.isEmpty(language)) { 442ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak String normalizedLanguage = sNormalizeLanguage.get(language); 443ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak if (normalizedLanguage != null) { 444ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak language = normalizedLanguage; 445ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak } 446ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak } 447ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak 448ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak String country = ttsLocale.getCountry(); 449ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak if (!TextUtils.isEmpty(country)) { 450ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak String normalizedCountry= sNormalizeCountry.get(country); 451ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak if (normalizedCountry != null) { 452ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak country = normalizedCountry; 453ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak } 454ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak } 455ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak return new Locale(language, country, ttsLocale.getVariant()); 456ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak } 457ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak 458ad6df74ada7c478257425b746588f22eeec199a6Przemyslaw Szczepaniak /** 4591b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * Return the old-style string form of the locale. It consists of 3 letter codes: 46040d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak * <ul> 4611b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * <li>"ISO 639-2/T language code" if the locale has no country entry</li> 4621b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country code" 4631b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * if the locale has no variant entry</li> 4641b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * <li> "ISO 639-2/T language code{@link #LOCALE_DELIMITER}ISO 3166 country 4651b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * code{@link #LOCALE_DELIMITER}variant" if the locale has a variant entry</li> 46640d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak * </ul> 4671b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * If we fail to generate those codes using {@link Locale#getISO3Country()} and 4681b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * {@link Locale#getISO3Language()}, then we return new String[]{"eng","USA",""}; 46940d51e70037c8d32f9bbafd54775e526d86caf23Przemyslaw Szczepaniak */ 4701b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak static public String[] toOldLocaleStringFormat(Locale locale) { 4711b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak String[] ret = new String[]{"","",""}; 47258f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak try { 47358f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak // Note that the default locale might have an empty variant 47401dedf59db924726ee323aac89708031ce8f320fNiels Egberts // or language. 4751b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak ret[0] = locale.getISO3Language(); 4761b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak ret[1] = locale.getISO3Country(); 4771b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak ret[2] = locale.getVariant(); 47858f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak 4791b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return ret; 48058f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak } catch (MissingResourceException e) { 48158f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the 48258f16653728417100c61b5cb63298e0e3bb528dcPrzemyslaw Szczepaniak // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US). 4831b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak return new String[]{"eng","USA",""}; 48439268ffcb74f4c177e5e7427b66480c77743f928Narayan Kamath } 485e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 486e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 487e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath /** 488e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * Parses a comma separated list of engine locale preferences. The list is of the 489e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * form {@code "engine_name_1:locale_1,engine_name_2:locale2"} and so on and 490e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * so forth. Returns null if the list is empty, malformed or if there is no engine 491e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * specific preference in the list. 492e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath */ 493e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath private static String parseEnginePrefFromList(String prefValue, String engineName) { 494e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (TextUtils.isEmpty(prefValue)) { 495e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath return null; 496e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 497e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 498e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath String[] prefValues = prefValue.split(","); 499e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 500e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath for (String value : prefValues) { 501e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath final int delimiter = value.indexOf(':'); 502e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (delimiter > 0) { 503e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (engineName.equals(value.substring(0, delimiter))) { 504e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath return value.substring(delimiter + 1); 505e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 506e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 507e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 508e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 509e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath return null; 510e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 511e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 5121b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak /** 5131b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * Serialize the locale to a string and store it as a default locale for the given engine. If 5141b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak * the passed locale is null, an empty string will be serialized; that empty string, when 5156dfa6e2a9be08a3a0f152a7b772efc8ce2469bcePrzemyslaw Szczepaniak * read back, will evaluate to {@link Locale#getDefault()}. 5161b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak */ 5171b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) { 518e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath final String prefList = Settings.Secure.getString(mContext.getContentResolver(), 519e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath Settings.Secure.TTS_DEFAULT_LOCALE); 520e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (DBG) { 5211b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak Log.d(TAG, "updateLocalePrefForEngine(" + engineName + ", " + newLocale + 522e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath "), originally: " + prefList); 523e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 524e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 525e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath final String newPrefList = updateValueInCommaSeparatedList(prefList, 5261b5637ee32c5d4e5d857fa86a1b1c1db23d027b7Przemyslaw Szczepaniak engineName, (newLocale != null) ? newLocale.toString() : ""); 527e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 528e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (DBG) Log.d(TAG, "updateLocalePrefForEngine(), writing: " + newPrefList.toString()); 529e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 530e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath Settings.Secure.putString(mContext.getContentResolver(), 531e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath Settings.Secure.TTS_DEFAULT_LOCALE, newPrefList.toString()); 532e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 533e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 534e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath /** 535e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * Updates the value for a given key in a comma separated list of key value pairs, 536e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * each of which are delimited by a colon. If no value exists for the given key, 537e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath * the kay value pair are appended to the end of the list. 538e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath */ 539e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath private String updateValueInCommaSeparatedList(String list, String key, 540e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath String newValue) { 541e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath StringBuilder newPrefList = new StringBuilder(); 542e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (TextUtils.isEmpty(list)) { 543e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // If empty, create a new list with a single entry. 544e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(key).append(':').append(newValue); 545e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } else { 546e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath String[] prefValues = list.split(","); 547e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // Whether this is the first iteration in the loop. 548e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath boolean first = true; 549e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // Whether we found the given key. 550e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath boolean found = false; 551e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath for (String value : prefValues) { 552e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath final int delimiter = value.indexOf(':'); 553e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (delimiter > 0) { 554e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (key.equals(value.substring(0, delimiter))) { 555e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (first) { 556e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath first = false; 557e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } else { 558e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(','); 559e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 560e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath found = true; 561e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(key).append(':').append(newValue); 562e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } else { 563e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (first) { 564e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath first = false; 565e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } else { 566e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(','); 567e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 568e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // Copy across the entire key + value as is. 569e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(value); 570e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 571e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 572e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 573e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 574e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath if (!found) { 575e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // Not found, but the rest of the keys would have been copied 576e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath // over already, so just append it to the end. 577e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(','); 578e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath newPrefList.append(key).append(':').append(newValue); 579e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 580e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 581e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath 582e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath return newPrefList.toString(); 583e5b8c4dfc70288f661e0da4f082dd51cc1399f86Narayan Kamath } 584d3ee2fa18464fb7e4d7f6d27610fbf60b6d1ffceNarayan Kamath} 585