• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.PackageManager;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.graphics.drawable.Drawable;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkInfo;
28 import android.os.AsyncTask;
29 import android.os.IBinder;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
34 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
35 import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
36 import com.android.inputmethod.deprecated.VoiceProxy;
37 import com.android.inputmethod.keyboard.KeyboardSwitcher;
38 import com.android.inputmethod.keyboard.LatinKeyboard;
39 
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.Map;
45 
46 public class SubtypeSwitcher {
47     private static boolean DBG = LatinImeLogger.sDBG;
48     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
49 
50     private static final char LOCALE_SEPARATER = '_';
51     private static final String KEYBOARD_MODE = "keyboard";
52     private static final String VOICE_MODE = "voice";
53     private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
54             "requireNetworkConnectivity";
55 
56     private final TextUtils.SimpleStringSplitter mLocaleSplitter =
57             new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
58 
59     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
60     private /* final */ LatinIME mService;
61     private /* final */ InputMethodManagerCompatWrapper mImm;
62     private /* final */ Resources mResources;
63     private /* final */ ConnectivityManager mConnectivityManager;
64     private final ArrayList<InputMethodSubtypeCompatWrapper>
65             mEnabledKeyboardSubtypesOfCurrentInputMethod =
66                     new ArrayList<InputMethodSubtypeCompatWrapper>();
67     private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
68 
69     /*-----------------------------------------------------------*/
70     // Variants which should be changed only by reload functions.
71     private boolean mNeedsToDisplayLanguage;
72     private boolean mIsSystemLanguageSameAsInputLanguage;
73     private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
74     private InputMethodSubtypeCompatWrapper mShortcutSubtype;
75     private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
76     private InputMethodSubtypeCompatWrapper mCurrentSubtype;
77     private Locale mSystemLocale;
78     private Locale mInputLocale;
79     private String mInputLocaleStr;
80     private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
81     /*-----------------------------------------------------------*/
82 
83     private boolean mIsNetworkConnected;
84 
getInstance()85     public static SubtypeSwitcher getInstance() {
86         return sInstance;
87     }
88 
init(LatinIME service)89     public static void init(LatinIME service) {
90         SubtypeLocale.init(service);
91         sInstance.initialize(service);
92         sInstance.updateAllParameters();
93     }
94 
SubtypeSwitcher()95     private SubtypeSwitcher() {
96         // Intentional empty constructor for singleton.
97     }
98 
initialize(LatinIME service)99     private void initialize(LatinIME service) {
100         mService = service;
101         mResources = service.getResources();
102         mImm = InputMethodManagerCompatWrapper.getInstance();
103         mConnectivityManager = (ConnectivityManager) service.getSystemService(
104                 Context.CONNECTIVITY_SERVICE);
105         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
106         mEnabledLanguagesOfCurrentInputMethod.clear();
107         mSystemLocale = null;
108         mInputLocale = null;
109         mInputLocaleStr = null;
110         mCurrentSubtype = null;
111         mAllEnabledSubtypesOfCurrentInputMethod = null;
112         mVoiceInputWrapper = null;
113 
114         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
115         mIsNetworkConnected = (info != null && info.isConnected());
116     }
117 
118     // Update all parameters stored in SubtypeSwitcher.
119     // Only configuration changed event is allowed to call this because this is heavy.
updateAllParameters()120     private void updateAllParameters() {
121         mSystemLocale = mResources.getConfiguration().locale;
122         updateSubtype(mImm.getCurrentInputMethodSubtype());
123         updateParametersOnStartInputView();
124     }
125 
126     // Update parameters which are changed outside LatinIME. This parameters affect UI so they
127     // should be updated every time onStartInputview.
updateParametersOnStartInputView()128     public void updateParametersOnStartInputView() {
129         updateEnabledSubtypes();
130         updateShortcutIME();
131     }
132 
133     // Reload enabledSubtypes from the framework.
updateEnabledSubtypes()134     private void updateEnabledSubtypes() {
135         final String currentMode = getCurrentSubtypeMode();
136         boolean foundCurrentSubtypeBecameDisabled = true;
137         mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
138                 null, true);
139         mEnabledLanguagesOfCurrentInputMethod.clear();
140         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
141         for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
142             final String locale = getSubtypeLocale(ims);
143             final String mode = ims.getMode();
144             mLocaleSplitter.setString(locale);
145             if (mLocaleSplitter.hasNext()) {
146                 mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next());
147             }
148             if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) {
149                 foundCurrentSubtypeBecameDisabled = false;
150             }
151             if (KEYBOARD_MODE.equals(ims.getMode())) {
152                 mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims);
153             }
154         }
155         mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
156                 && mIsSystemLanguageSameAsInputLanguage);
157         if (foundCurrentSubtypeBecameDisabled) {
158             if (DBG) {
159                 Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode);
160                 Log.w(TAG, "Last subtype was disabled. Update to the current one.");
161             }
162             updateSubtype(mImm.getCurrentInputMethodSubtype());
163         }
164     }
165 
updateShortcutIME()166     private void updateShortcutIME() {
167         if (DBG) {
168             Log.d(TAG, "Update shortcut IME from : "
169                     + (mShortcutInputMethodInfo == null
170                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
171                     + (mShortcutSubtype == null ? "<null>" : (getSubtypeLocale(mShortcutSubtype)
172                             + ", " + mShortcutSubtype.getMode())));
173         }
174         // TODO: Update an icon for shortcut IME
175         final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
176                 mImm.getShortcutInputMethodsAndSubtypes();
177         mShortcutInputMethodInfo = null;
178         mShortcutSubtype = null;
179         for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
180             List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
181             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
182             // appropriate.
183             mShortcutInputMethodInfo = imi;
184             // TODO: Pick up the first found subtype for now. Should handle all subtypes
185             // as appropriate.
186             mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
187             break;
188         }
189         if (DBG) {
190             Log.d(TAG, "Update shortcut IME to : "
191                     + (mShortcutInputMethodInfo == null
192                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
193                     + (mShortcutSubtype == null ? "<null>" : (getSubtypeLocale(mShortcutSubtype)
194                             + ", " + mShortcutSubtype.getMode())));
195         }
196     }
197 
getSubtypeLocale(InputMethodSubtypeCompatWrapper subtype)198     private static String getSubtypeLocale(InputMethodSubtypeCompatWrapper subtype) {
199         final String keyboardLocale = subtype.getExtraValueOf(
200                 LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE);
201         return keyboardLocale != null ? keyboardLocale : subtype.getLocale();
202     }
203 
204     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
updateSubtype(InputMethodSubtypeCompatWrapper newSubtype)205     public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
206         final String newLocale;
207         final String newMode;
208         final String oldMode = getCurrentSubtypeMode();
209         if (newSubtype == null) {
210             // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
211             // fallback to the default locale.
212             Log.w(TAG, "Couldn't get the current subtype.");
213             newLocale = "en_US";
214             newMode = KEYBOARD_MODE;
215         } else {
216             newLocale = getSubtypeLocale(newSubtype);
217             newMode = newSubtype.getMode();
218         }
219         if (DBG) {
220             Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
221                     + ", from: " + mInputLocaleStr + ", " + oldMode);
222         }
223         boolean languageChanged = false;
224         if (!newLocale.equals(mInputLocaleStr)) {
225             if (mInputLocaleStr != null) {
226                 languageChanged = true;
227             }
228             updateInputLocale(newLocale);
229         }
230         boolean modeChanged = false;
231         if (!newMode.equals(oldMode)) {
232             if (oldMode != null) {
233                 modeChanged = true;
234             }
235         }
236         mCurrentSubtype = newSubtype;
237 
238         // If the old mode is voice input, we need to reset or cancel its status.
239         // We cancel its status when we change mode, while we reset otherwise.
240         if (isKeyboardMode()) {
241             if (modeChanged) {
242                 if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
243                     mVoiceInputWrapper.cancel();
244                 }
245             }
246             if (modeChanged || languageChanged) {
247                 updateShortcutIME();
248                 mService.onRefreshKeyboard();
249             }
250         } else if (isVoiceMode() && mVoiceInputWrapper != null) {
251             if (VOICE_MODE.equals(oldMode)) {
252                 mVoiceInputWrapper.reset();
253             }
254             // If needsToShowWarningDialog is true, voice input need to show warning before
255             // show recognition view.
256             if (languageChanged || modeChanged
257                     || VoiceProxy.getInstance().needsToShowWarningDialog()) {
258                 triggerVoiceIME();
259             }
260         } else {
261             if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
262                 // We need to reset the voice input to release the resources and to reset its status
263                 // as it is not the current input mode.
264                 mVoiceInputWrapper.reset();
265             }
266             final String packageName = mService.getPackageName();
267             int version = -1;
268             try {
269                 version = mService.getPackageManager().getPackageInfo(
270                         packageName, 0).versionCode;
271             } catch (NameNotFoundException e) {
272             }
273             Log.w(TAG, "Unknown subtype mode: " + newMode + "," + version + ", " + packageName
274                     + ", " + mVoiceInputWrapper + ". IME is already changed to other IME.");
275             if (newSubtype != null) {
276                 Log.w(TAG, "Subtype mode:" + newSubtype.getMode());
277                 Log.w(TAG, "Subtype locale:" + newSubtype.getLocale());
278                 Log.w(TAG, "Subtype extra value:" + newSubtype.getExtraValue());
279                 Log.w(TAG, "Subtype is auxiliary:" + newSubtype.isAuxiliary());
280             }
281         }
282     }
283 
284     // Update the current input locale from Locale string.
updateInputLocale(String inputLocaleStr)285     private void updateInputLocale(String inputLocaleStr) {
286         // example: inputLocaleStr = "en_US" "en" ""
287         // "en_US" --> language: en  & country: US
288         // "en" --> language: en
289         // "" --> the system locale
290         if (!TextUtils.isEmpty(inputLocaleStr)) {
291             mInputLocale = LocaleUtils.constructLocaleFromString(inputLocaleStr);
292             mInputLocaleStr = inputLocaleStr;
293         } else {
294             mInputLocale = mSystemLocale;
295             String country = mSystemLocale.getCountry();
296             mInputLocaleStr = mSystemLocale.getLanguage()
297                     + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage());
298         }
299         mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase(
300                 getInputLocale().getLanguage());
301         mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
302                 && mIsSystemLanguageSameAsInputLanguage);
303     }
304 
305     ////////////////////////////
306     // Shortcut IME functions //
307     ////////////////////////////
308 
switchToShortcutIME()309     public void switchToShortcutIME() {
310         if (mShortcutInputMethodInfo == null) {
311             return;
312         }
313 
314         final String imiId = mShortcutInputMethodInfo.getId();
315         final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
316         switchToTargetIME(imiId, subtype);
317     }
318 
switchToTargetIME( final String imiId, final InputMethodSubtypeCompatWrapper subtype)319     private void switchToTargetIME(
320             final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
321         final IBinder token = mService.getWindow().getWindow().getAttributes().token;
322         if (token == null) {
323             return;
324         }
325         new AsyncTask<Void, Void, Void>() {
326             @Override
327             protected Void doInBackground(Void... params) {
328                 mImm.setInputMethodAndSubtype(token, imiId, subtype);
329                 return null;
330             }
331 
332             @Override
333             protected void onPostExecute(Void result) {
334                 // Calls in this method need to be done in the same thread as the thread which
335                 // called switchToShortcutIME().
336 
337                 // Notify an event that the current subtype was changed. This event will be
338                 // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
339                 // when the API level is 10 or previous.
340                 mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
341             }
342         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
343     }
344 
getShortcutIcon()345     public Drawable getShortcutIcon() {
346         return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
347     }
348 
getSubtypeIcon( InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype)349     private Drawable getSubtypeIcon(
350             InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
351         final PackageManager pm = mService.getPackageManager();
352         if (imi != null) {
353             final String imiPackageName = imi.getPackageName();
354             if (DBG) {
355                 Log.d(TAG, "Update icons of IME: " + imiPackageName + ","
356                         + getSubtypeLocale(subtype) + "," + subtype.getMode());
357             }
358             if (subtype != null) {
359                 return pm.getDrawable(imiPackageName, subtype.getIconResId(),
360                         imi.getServiceInfo().applicationInfo);
361             } else if (imi.getSubtypeCount() > 0 && imi.getSubtypeAt(0) != null) {
362                 return pm.getDrawable(imiPackageName,
363                         imi.getSubtypeAt(0).getIconResId(),
364                         imi.getServiceInfo().applicationInfo);
365             } else {
366                 try {
367                     return pm.getApplicationInfo(imiPackageName, 0).loadIcon(pm);
368                 } catch (PackageManager.NameNotFoundException e) {
369                     Log.w(TAG, "IME can't be found: " + imiPackageName);
370                 }
371             }
372         }
373         return null;
374     }
375 
contains(String[] hay, String needle)376     private static boolean contains(String[] hay, String needle) {
377         for (String element : hay) {
378             if (element.equals(needle))
379                 return true;
380         }
381         return false;
382     }
383 
isShortcutImeEnabled()384     public boolean isShortcutImeEnabled() {
385         if (mShortcutInputMethodInfo == null) {
386             return false;
387         }
388         if (mShortcutSubtype == null) {
389             return true;
390         }
391         // For compatibility, if the shortcut subtype is dummy, we assume the shortcut IME
392         // (built-in voice dummy subtype) is available.
393         if (!mShortcutSubtype.hasOriginalObject()) {
394             return true;
395         }
396         final boolean allowsImplicitlySelectedSubtypes = true;
397         for (final InputMethodSubtypeCompatWrapper enabledSubtype :
398                 mImm.getEnabledInputMethodSubtypeList(
399                         mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
400             if (enabledSubtype.equals(mShortcutSubtype)) {
401                 return true;
402             }
403         }
404         return false;
405     }
406 
isShortcutImeReady()407     public boolean isShortcutImeReady() {
408         if (mShortcutInputMethodInfo == null)
409             return false;
410         if (mShortcutSubtype == null)
411             return true;
412         if (contains(mShortcutSubtype.getExtraValue().split(","),
413                 SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
414             return mIsNetworkConnected;
415         }
416         return true;
417     }
418 
onNetworkStateChanged(Intent intent)419     public void onNetworkStateChanged(Intent intent) {
420         final boolean noConnection = intent.getBooleanExtra(
421                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
422         mIsNetworkConnected = !noConnection;
423 
424         final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
425         final LatinKeyboard keyboard = switcher.getLatinKeyboard();
426         if (keyboard != null) {
427             keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
428         }
429     }
430 
431     //////////////////////////////////
432     // Language Switching functions //
433     //////////////////////////////////
434 
getEnabledKeyboardLocaleCount()435     public int getEnabledKeyboardLocaleCount() {
436         return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
437     }
438 
needsToDisplayLanguage(Locale keyboardLocale)439     public boolean needsToDisplayLanguage(Locale keyboardLocale) {
440         if (!keyboardLocale.equals(mInputLocale)) {
441             return false;
442         }
443         return mNeedsToDisplayLanguage;
444     }
445 
getInputLocale()446     public Locale getInputLocale() {
447         return mInputLocale;
448     }
449 
getInputLocaleStr()450     public String getInputLocaleStr() {
451         return mInputLocaleStr;
452     }
453 
getEnabledLanguages()454     public String[] getEnabledLanguages() {
455         int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
456         // Workaround for explicitly specifying the voice language
457         if (enabledLanguageCount == 1) {
458             mEnabledLanguagesOfCurrentInputMethod.add(mEnabledLanguagesOfCurrentInputMethod
459                     .get(0));
460             ++enabledLanguageCount;
461         }
462         return mEnabledLanguagesOfCurrentInputMethod.toArray(new String[enabledLanguageCount]);
463     }
464 
getSystemLocale()465     public Locale getSystemLocale() {
466         return mSystemLocale;
467     }
468 
isSystemLanguageSameAsInputLanguage()469     public boolean isSystemLanguageSameAsInputLanguage() {
470         return mIsSystemLanguageSameAsInputLanguage;
471     }
472 
onConfigurationChanged(Configuration conf)473     public void onConfigurationChanged(Configuration conf) {
474         final Locale systemLocale = conf.locale;
475         // If system configuration was changed, update all parameters.
476         if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
477             updateAllParameters();
478         }
479     }
480 
isKeyboardMode()481     public boolean isKeyboardMode() {
482         return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
483     }
484 
485 
486     ///////////////////////////
487     // Voice Input functions //
488     ///////////////////////////
489 
setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi)490     public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
491         if (mVoiceInputWrapper == null && vi != null) {
492             mVoiceInputWrapper = vi;
493             if (isVoiceMode()) {
494                 if (DBG) {
495                     Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
496                 }
497                 triggerVoiceIME();
498                 return true;
499             }
500         }
501         return false;
502     }
503 
isVoiceMode()504     public boolean isVoiceMode() {
505         return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
506     }
507 
isDummyVoiceMode()508     public boolean isDummyVoiceMode() {
509         return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
510                 && VOICE_MODE.equals(getCurrentSubtypeMode());
511     }
512 
triggerVoiceIME()513     private void triggerVoiceIME() {
514         if (!mService.isInputViewShown()) return;
515         VoiceProxy.getInstance().startListening(false,
516                 KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken());
517     }
518 
getInputLanguageName()519     public String getInputLanguageName() {
520         return Utils.getDisplayLanguage(getInputLocale());
521     }
522 
523     /////////////////////////////
524     // Other utility functions //
525     /////////////////////////////
526 
getCurrentSubtypeExtraValue()527     public String getCurrentSubtypeExtraValue() {
528         // If null, return what an empty ExtraValue would return : the empty string.
529         return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : "";
530     }
531 
currentSubtypeContainsExtraValueKey(String key)532     public boolean currentSubtypeContainsExtraValueKey(String key) {
533         // If null, return what an empty ExtraValue would return : false.
534         return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false;
535     }
536 
getCurrentSubtypeExtraValueOf(String key)537     public String getCurrentSubtypeExtraValueOf(String key) {
538         // If null, return what an empty ExtraValue would return : null.
539         return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null;
540     }
541 
getCurrentSubtypeMode()542     public String getCurrentSubtypeMode() {
543         return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
544     }
545 
546 
isVoiceSupported(Context context, String locale)547     public static boolean isVoiceSupported(Context context, String locale) {
548         // Get the current list of supported locales and check the current locale against that
549         // list. We cache this value so as not to check it every time the user starts a voice
550         // input. Because this method is called by onStartInputView, this should mean that as
551         // long as the locale doesn't change while the user is keeping the IME open, the
552         // value should never be stale.
553         String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
554                 context.getContentResolver());
555         List<String> voiceInputSupportedLocales = Arrays.asList(
556                 supportedLocalesString.split("\\s+"));
557         return voiceInputSupportedLocales.contains(locale);
558     }
559 }
560