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