Home | History | Annotate | Download | only in latin
      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 
     85     public static SubtypeSwitcher getInstance() {
     86         return sInstance;
     87     }
     88 
     89     public static void init(LatinIME service) {
     90         SubtypeLocale.init(service);
     91         sInstance.initialize(service);
     92         sInstance.updateAllParameters();
     93     }
     94 
     95     private SubtypeSwitcher() {
     96         // Intentional empty constructor for singleton.
     97     }
     98 
     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.
    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.
    128     public void updateParametersOnStartInputView() {
    129         updateEnabledSubtypes();
    130         updateShortcutIME();
    131     }
    132 
    133     // Reload enabledSubtypes from the framework.
    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 
    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 
    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.
    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.
    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 
    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 
    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 
    345     public Drawable getShortcutIcon() {
    346         return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
    347     }
    348 
    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 
    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 
    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 
    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 
    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 
    435     public int getEnabledKeyboardLocaleCount() {
    436         return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
    437     }
    438 
    439     public boolean needsToDisplayLanguage(Locale keyboardLocale) {
    440         if (!keyboardLocale.equals(mInputLocale)) {
    441             return false;
    442         }
    443         return mNeedsToDisplayLanguage;
    444     }
    445 
    446     public Locale getInputLocale() {
    447         return mInputLocale;
    448     }
    449 
    450     public String getInputLocaleStr() {
    451         return mInputLocaleStr;
    452     }
    453 
    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 
    465     public Locale getSystemLocale() {
    466         return mSystemLocale;
    467     }
    468 
    469     public boolean isSystemLanguageSameAsInputLanguage() {
    470         return mIsSystemLanguageSameAsInputLanguage;
    471     }
    472 
    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 
    481     public boolean isKeyboardMode() {
    482         return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
    483     }
    484 
    485 
    486     ///////////////////////////
    487     // Voice Input functions //
    488     ///////////////////////////
    489 
    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 
    504     public boolean isVoiceMode() {
    505         return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
    506     }
    507 
    508     public boolean isDummyVoiceMode() {
    509         return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
    510                 && VOICE_MODE.equals(getCurrentSubtypeMode());
    511     }
    512 
    513     private void triggerVoiceIME() {
    514         if (!mService.isInputViewShown()) return;
    515         VoiceProxy.getInstance().startListening(false,
    516                 KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken());
    517     }
    518 
    519     public String getInputLanguageName() {
    520         return Utils.getDisplayLanguage(getInputLocale());
    521     }
    522 
    523     /////////////////////////////
    524     // Other utility functions //
    525     /////////////////////////////
    526 
    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 
    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 
    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 
    542     public String getCurrentSubtypeMode() {
    543         return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
    544     }
    545 
    546 
    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