SubtypeSwitcher.java revision c150acc7c85ff2f5eeb5bd2c6ff288df4e46a355
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
17package com.android.inputmethod.latin;
18
19import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
20import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
21import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
22import com.android.inputmethod.deprecated.VoiceProxy;
23import com.android.inputmethod.keyboard.KeyboardSwitcher;
24import com.android.inputmethod.keyboard.LatinKeyboard;
25
26import android.content.Context;
27import android.content.Intent;
28import android.content.SharedPreferences;
29import android.content.pm.PackageManager;
30import android.content.res.Configuration;
31import android.content.res.Resources;
32import android.graphics.drawable.Drawable;
33import android.net.ConnectivityManager;
34import android.net.NetworkInfo;
35import android.os.AsyncTask;
36import android.os.IBinder;
37import android.text.TextUtils;
38import android.util.Log;
39
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.List;
43import java.util.Locale;
44import java.util.Map;
45
46public 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    public static final String USE_SPACEBAR_LANGUAGE_SWITCH_KEY = "use_spacebar_language_switch";
56
57    private final TextUtils.SimpleStringSplitter mLocaleSplitter =
58            new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
59
60    private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
61    private /* final */ LatinIME mService;
62    private /* final */ InputMethodManagerCompatWrapper mImm;
63    private /* final */ Resources mResources;
64    private /* final */ ConnectivityManager mConnectivityManager;
65    private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
66    private /* final */ SharedPreferences mPrefs;
67    private final ArrayList<InputMethodSubtypeCompatWrapper>
68            mEnabledKeyboardSubtypesOfCurrentInputMethod =
69                    new ArrayList<InputMethodSubtypeCompatWrapper>();
70    private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
71    private final LanguageBarInfo mLanguageBarInfo = new LanguageBarInfo();
72
73    /*-----------------------------------------------------------*/
74    // Variants which should be changed only by reload functions.
75    private boolean mNeedsToDisplayLanguage;
76    private boolean mIsSystemLanguageSameAsInputLanguage;
77    private InputMethodInfoCompatWrapper mShortcutInputMethodInfo;
78    private InputMethodSubtypeCompatWrapper mShortcutSubtype;
79    private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod;
80    private InputMethodSubtypeCompatWrapper mCurrentSubtype;
81    private Locale mSystemLocale;
82    private Locale mInputLocale;
83    private String mInputLocaleStr;
84    private String mInputMethodId;
85    private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
86    /*-----------------------------------------------------------*/
87
88    private boolean mIsNetworkConnected;
89
90    public static SubtypeSwitcher getInstance() {
91        return sInstance;
92    }
93
94    public static void init(LatinIME service, SharedPreferences prefs) {
95        SubtypeLocale.init(service);
96        sInstance.initialize(service, prefs);
97        sInstance.updateAllParameters();
98    }
99
100    private SubtypeSwitcher() {
101        // Intentional empty constructor for singleton.
102    }
103
104    private void initialize(LatinIME service, SharedPreferences prefs) {
105        mService = service;
106        mResources = service.getResources();
107        mImm = InputMethodManagerCompatWrapper.getInstance(service);
108        mConnectivityManager = (ConnectivityManager) service.getSystemService(
109                Context.CONNECTIVITY_SERVICE);
110        mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
111        mEnabledLanguagesOfCurrentInputMethod.clear();
112        mSystemLocale = null;
113        mInputLocale = null;
114        mInputLocaleStr = null;
115        mCurrentSubtype = null;
116        mAllEnabledSubtypesOfCurrentInputMethod = null;
117        mVoiceInputWrapper = null;
118        mPrefs = prefs;
119
120        final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
121        mIsNetworkConnected = (info != null && info.isConnected());
122        mInputMethodId = Utils.getInputMethodId(mImm, service.getPackageName());
123    }
124
125    // Update all parameters stored in SubtypeSwitcher.
126    // Only configuration changed event is allowed to call this because this is heavy.
127    private void updateAllParameters() {
128        mSystemLocale = mResources.getConfiguration().locale;
129        updateSubtype(mImm.getCurrentInputMethodSubtype());
130        updateParametersOnStartInputView();
131    }
132
133    // Update parameters which are changed outside LatinIME. This parameters affect UI so they
134    // should be updated every time onStartInputview.
135    public void updateParametersOnStartInputView() {
136        mConfigUseSpacebarLanguageSwitcher = mPrefs.getBoolean(USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
137                mService.getResources().getBoolean(
138                        R.bool.config_use_spacebar_language_switcher));
139        updateEnabledSubtypes();
140        updateShortcutIME();
141    }
142
143    // Reload enabledSubtypes from the framework.
144    private void updateEnabledSubtypes() {
145        final String currentMode = getCurrentSubtypeMode();
146        boolean foundCurrentSubtypeBecameDisabled = true;
147        mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
148                null, true);
149        mEnabledLanguagesOfCurrentInputMethod.clear();
150        mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
151        for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
152            final String locale = ims.getLocale();
153            final String mode = ims.getMode();
154            mLocaleSplitter.setString(locale);
155            if (mLocaleSplitter.hasNext()) {
156                mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next());
157            }
158            if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) {
159                foundCurrentSubtypeBecameDisabled = false;
160            }
161            if (KEYBOARD_MODE.equals(ims.getMode())) {
162                mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims);
163            }
164        }
165        mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
166                && mIsSystemLanguageSameAsInputLanguage);
167        if (foundCurrentSubtypeBecameDisabled) {
168            if (DBG) {
169                Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode);
170                Log.w(TAG, "Last subtype was disabled. Update to the current one.");
171            }
172            updateSubtype(mImm.getCurrentInputMethodSubtype());
173        } else {
174            // mLanguageBarInfo.update() will be called in updateSubtype so there is no need
175            // to call this in the if-clause above.
176            mLanguageBarInfo.update();
177        }
178    }
179
180    private void updateShortcutIME() {
181        if (DBG) {
182            Log.d(TAG, "Update shortcut IME from : "
183                    + (mShortcutInputMethodInfo == null
184                            ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
185                    + (mShortcutSubtype == null ? "<null>" : (mShortcutSubtype.getLocale()
186                            + ", " + mShortcutSubtype.getMode())));
187        }
188        // TODO: Update an icon for shortcut IME
189        final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts =
190                mImm.getShortcutInputMethodsAndSubtypes();
191        for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) {
192            List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi);
193            // TODO: Returns the first found IMI for now. Should handle all shortcuts as
194            // appropriate.
195            mShortcutInputMethodInfo = imi;
196            // TODO: Pick up the first found subtype for now. Should handle all subtypes
197            // as appropriate.
198            mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
199            break;
200        }
201        if (DBG) {
202            Log.d(TAG, "Update shortcut IME to : "
203                    + (mShortcutInputMethodInfo == null
204                            ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
205                    + (mShortcutSubtype == null ? "<null>" : (mShortcutSubtype.getLocale()
206                            + ", " + mShortcutSubtype.getMode())));
207        }
208    }
209
210    // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
211    public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) {
212        final String newLocale;
213        final String newMode;
214        final String oldMode = getCurrentSubtypeMode();
215        if (newSubtype == null) {
216            // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
217            // fallback to the default locale.
218            Log.w(TAG, "Couldn't get the current subtype.");
219            newLocale = "en_US";
220            newMode = KEYBOARD_MODE;
221        } else {
222            newLocale = newSubtype.getLocale();
223            newMode = newSubtype.getMode();
224        }
225        if (DBG) {
226            Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
227                    + ", from: " + mInputLocaleStr + ", " + oldMode);
228        }
229        boolean languageChanged = false;
230        if (!newLocale.equals(mInputLocaleStr)) {
231            if (mInputLocaleStr != null) {
232                languageChanged = true;
233            }
234            updateInputLocale(newLocale);
235        }
236        boolean modeChanged = false;
237        if (!newMode.equals(oldMode)) {
238            if (oldMode != null) {
239                modeChanged = true;
240            }
241        }
242        mCurrentSubtype = newSubtype;
243
244        // If the old mode is voice input, we need to reset or cancel its status.
245        // We cancel its status when we change mode, while we reset otherwise.
246        if (isKeyboardMode()) {
247            if (modeChanged) {
248                if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
249                    mVoiceInputWrapper.cancel();
250                }
251            }
252            if (modeChanged || languageChanged) {
253                updateShortcutIME();
254                mService.onRefreshKeyboard();
255            }
256        } else if (isVoiceMode() && mVoiceInputWrapper != null) {
257            if (VOICE_MODE.equals(oldMode)) {
258                mVoiceInputWrapper.reset();
259            }
260            // If needsToShowWarningDialog is true, voice input need to show warning before
261            // show recognition view.
262            if (languageChanged || modeChanged
263                    || VoiceProxy.getInstance().needsToShowWarningDialog()) {
264                triggerVoiceIME();
265            }
266        } else {
267            Log.w(TAG, "Unknown subtype mode: " + newMode);
268            if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) {
269                // We need to reset the voice input to release the resources and to reset its status
270                // as it is not the current input mode.
271                mVoiceInputWrapper.reset();
272            }
273        }
274        mLanguageBarInfo.update();
275    }
276
277    // Update the current input locale from Locale string.
278    private void updateInputLocale(String inputLocaleStr) {
279        // example: inputLocaleStr = "en_US" "en" ""
280        // "en_US" --> language: en  & country: US
281        // "en" --> language: en
282        // "" --> the system locale
283        if (!TextUtils.isEmpty(inputLocaleStr)) {
284            mInputLocale = Utils.constructLocaleFromString(inputLocaleStr);
285            mInputLocaleStr = inputLocaleStr;
286        } else {
287            mInputLocale = mSystemLocale;
288            String country = mSystemLocale.getCountry();
289            mInputLocaleStr = mSystemLocale.getLanguage()
290                    + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage());
291        }
292        mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase(
293                getInputLocale().getLanguage());
294        mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
295                && mIsSystemLanguageSameAsInputLanguage);
296    }
297
298    ////////////////////////////
299    // Shortcut IME functions //
300    ////////////////////////////
301
302    public void switchToShortcutIME() {
303        if (mShortcutInputMethodInfo == null) {
304            return;
305        }
306
307        final String imiId = mShortcutInputMethodInfo.getId();
308        final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
309        switchToTargetIME(imiId, subtype);
310    }
311
312    private void switchToTargetIME(
313            final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
314        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
315        if (token == null) {
316            return;
317        }
318        new AsyncTask<Void, Void, Void>() {
319            @Override
320            protected Void doInBackground(Void... params) {
321                mImm.setInputMethodAndSubtype(token, imiId, subtype);
322                return null;
323            }
324
325            @Override
326            protected void onPostExecute(Void result) {
327                // Calls in this method need to be done in the same thread as the thread which
328                // called switchToShortcutIME().
329
330                // Notify an event that the current subtype was changed. This event will be
331                // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented
332                // when the API level is 10 or previous.
333                mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
334            }
335        }.execute();
336    }
337
338    public Drawable getShortcutIcon() {
339        return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
340    }
341
342    private Drawable getSubtypeIcon(
343            InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) {
344        final PackageManager pm = mService.getPackageManager();
345        if (imi != null) {
346            final String imiPackageName = imi.getPackageName();
347            if (DBG) {
348                Log.d(TAG, "Update icons of IME: " + imiPackageName + ","
349                        + subtype.getLocale() + "," + subtype.getMode());
350            }
351            if (subtype != null) {
352                return pm.getDrawable(imiPackageName, subtype.getIconResId(),
353                        imi.getServiceInfo().applicationInfo);
354            } else if (imi.getSubtypeCount() > 0 && imi.getSubtypeAt(0) != null) {
355                return pm.getDrawable(imiPackageName,
356                        imi.getSubtypeAt(0).getIconResId(),
357                        imi.getServiceInfo().applicationInfo);
358            } else {
359                try {
360                    return pm.getApplicationInfo(imiPackageName, 0).loadIcon(pm);
361                } catch (PackageManager.NameNotFoundException e) {
362                    Log.w(TAG, "IME can't be found: " + imiPackageName);
363                }
364            }
365        }
366        return null;
367    }
368
369    private static boolean contains(String[] hay, String needle) {
370        for (String element : hay) {
371            if (element.equals(needle))
372                return true;
373        }
374        return false;
375    }
376
377    public boolean isShortcutImeEnabled() {
378        if (mShortcutInputMethodInfo == null)
379            return false;
380        if (mShortcutSubtype == null)
381            return true;
382        // For compatibility, if the shortcut subtype is dummy, we assume the shortcut IME
383        // (built-in voice dummy subtype) is available.
384        if (!mShortcutSubtype.hasOriginalObject()) return true;
385        final boolean allowsImplicitlySelectedSubtypes = true;
386        for (final InputMethodSubtypeCompatWrapper enabledSubtype :
387                mImm.getEnabledInputMethodSubtypeList(
388                        mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
389            if (enabledSubtype.equals(mShortcutSubtype)) {
390                return true;
391            }
392        }
393        return false;
394    }
395
396    public boolean isShortcutImeReady() {
397        if (mShortcutInputMethodInfo == null)
398            return false;
399        if (mShortcutSubtype == null)
400            return true;
401        if (contains(mShortcutSubtype.getExtraValue().split(","),
402                SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
403            return mIsNetworkConnected;
404        }
405        return true;
406    }
407
408    public void onNetworkStateChanged(Intent intent) {
409        final boolean noConnection = intent.getBooleanExtra(
410                ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
411        mIsNetworkConnected = !noConnection;
412
413        final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
414        final LatinKeyboard keyboard = switcher.getLatinKeyboard();
415        if (keyboard != null) {
416            keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
417        }
418    }
419
420    //////////////////////////////////
421    // Language Switching functions //
422    //////////////////////////////////
423
424    public int getEnabledKeyboardLocaleCount() {
425        return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
426    }
427
428    public boolean useSpacebarLanguageSwitcher() {
429        return mConfigUseSpacebarLanguageSwitcher;
430    }
431
432    public boolean needsToDisplayLanguage() {
433        return mNeedsToDisplayLanguage;
434    }
435
436    public Locale getInputLocale() {
437        return mInputLocale;
438    }
439
440    public String getInputLocaleStr() {
441        return mInputLocaleStr;
442    }
443
444    public String[] getEnabledLanguages() {
445        int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
446        // Workaround for explicitly specifying the voice language
447        if (enabledLanguageCount == 1) {
448            mEnabledLanguagesOfCurrentInputMethod.add(mEnabledLanguagesOfCurrentInputMethod
449                    .get(0));
450            ++enabledLanguageCount;
451        }
452        return mEnabledLanguagesOfCurrentInputMethod.toArray(new String[enabledLanguageCount]);
453    }
454
455    public Locale getSystemLocale() {
456        return mSystemLocale;
457    }
458
459    public boolean isSystemLanguageSameAsInputLanguage() {
460        return mIsSystemLanguageSameAsInputLanguage;
461    }
462
463    public void onConfigurationChanged(Configuration conf) {
464        final Locale systemLocale = conf.locale;
465        // If system configuration was changed, update all parameters.
466        if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
467            updateAllParameters();
468        }
469    }
470
471    public boolean isKeyboardMode() {
472        return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
473    }
474
475
476    ///////////////////////////
477    // Voice Input functions //
478    ///////////////////////////
479
480    public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) {
481        if (mVoiceInputWrapper == null && vi != null) {
482            mVoiceInputWrapper = vi;
483            if (isVoiceMode()) {
484                if (DBG) {
485                    Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
486                }
487                triggerVoiceIME();
488                return true;
489            }
490        }
491        return false;
492    }
493
494    public boolean isVoiceMode() {
495        return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
496    }
497
498    public boolean isDummyVoiceMode() {
499        return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
500                && VOICE_MODE.equals(getCurrentSubtypeMode());
501    }
502
503    private void triggerVoiceIME() {
504        if (!mService.isInputViewShown()) return;
505        VoiceProxy.getInstance().startListening(false,
506                KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken());
507    }
508
509    //////////////////////////////////////
510    // Spacebar Language Switch support //
511    //////////////////////////////////////
512
513    private class LanguageBarInfo {
514        private int mCurrentKeyboardSubtypeIndex;
515        private InputMethodSubtypeCompatWrapper mNextKeyboardSubtype;
516        private InputMethodSubtypeCompatWrapper mPreviousKeyboardSubtype;
517        private String mNextLanguage;
518        private String mPreviousLanguage;
519        public LanguageBarInfo() {
520            update();
521        }
522
523        private String getNextLanguage() {
524            return mNextLanguage;
525        }
526
527        private String getPreviousLanguage() {
528            return mPreviousLanguage;
529        }
530
531        public InputMethodSubtypeCompatWrapper getNextKeyboardSubtype() {
532            return mNextKeyboardSubtype;
533        }
534
535        public InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtype() {
536            return mPreviousKeyboardSubtype;
537        }
538
539        public void update() {
540            if (!mConfigUseSpacebarLanguageSwitcher
541                    || mEnabledKeyboardSubtypesOfCurrentInputMethod == null
542                    || mEnabledKeyboardSubtypesOfCurrentInputMethod.size() == 0) return;
543            mCurrentKeyboardSubtypeIndex = getCurrentIndex();
544            mNextKeyboardSubtype = getNextKeyboardSubtypeInternal(mCurrentKeyboardSubtypeIndex);
545            Locale locale = Utils.constructLocaleFromString(mNextKeyboardSubtype.getLocale());
546            mNextLanguage = getFullDisplayName(locale, true);
547            mPreviousKeyboardSubtype = getPreviousKeyboardSubtypeInternal(
548                    mCurrentKeyboardSubtypeIndex);
549            locale = Utils.constructLocaleFromString(mPreviousKeyboardSubtype.getLocale());
550            mPreviousLanguage = getFullDisplayName(locale, true);
551        }
552
553        private int normalize(int index) {
554            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
555            final int ret = index % N;
556            return ret < 0 ? ret + N : ret;
557        }
558
559        private int getCurrentIndex() {
560            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
561            for (int i = 0; i < N; ++i) {
562                if (mEnabledKeyboardSubtypesOfCurrentInputMethod.get(i).equals(mCurrentSubtype)) {
563                    return i;
564                }
565            }
566            return 0;
567        }
568
569        private InputMethodSubtypeCompatWrapper getNextKeyboardSubtypeInternal(int index) {
570            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index + 1));
571        }
572
573        private InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtypeInternal(int index) {
574            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index - 1));
575        }
576    }
577
578    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
579        if (returnsNameInThisLocale) {
580            return toTitleCase(SubtypeLocale.getFullDisplayName(locale));
581        } else {
582            return toTitleCase(locale.getDisplayName());
583        }
584    }
585
586    public static String getDisplayLanguage(Locale locale) {
587        return toTitleCase(SubtypeLocale.getFullDisplayName(locale));
588    }
589
590    public static String getMiddleDisplayLanguage(Locale locale) {
591        return toTitleCase((Utils.constructLocaleFromString(
592                locale.getLanguage()).getDisplayLanguage(locale)));
593    }
594
595    public static String getShortDisplayLanguage(Locale locale) {
596        return toTitleCase(locale.getLanguage());
597    }
598
599    private static String toTitleCase(String s) {
600        if (s.length() == 0) {
601            return s;
602        }
603        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
604    }
605
606    public String getInputLanguageName() {
607        return getDisplayLanguage(getInputLocale());
608    }
609
610    public String getNextInputLanguageName() {
611        return mLanguageBarInfo.getNextLanguage();
612    }
613
614    public String getPreviousInputLanguageName() {
615        return mLanguageBarInfo.getPreviousLanguage();
616    }
617
618    /////////////////////////////
619    // Other utility functions //
620    /////////////////////////////
621
622    public String getCurrentSubtypeExtraValue() {
623        // If null, return what an empty ExtraValue would return : the empty string.
624        return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : "";
625    }
626
627    public boolean currentSubtypeContainsExtraValueKey(String key) {
628        // If null, return what an empty ExtraValue would return : false.
629        return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false;
630    }
631
632    public String getCurrentSubtypeExtraValueOf(String key) {
633        // If null, return what an empty ExtraValue would return : null.
634        return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null;
635    }
636
637    public String getCurrentSubtypeMode() {
638        return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
639    }
640
641
642    public boolean isVoiceSupported(String locale) {
643        // Get the current list of supported locales and check the current locale against that
644        // list. We cache this value so as not to check it every time the user starts a voice
645        // input. Because this method is called by onStartInputView, this should mean that as
646        // long as the locale doesn't change while the user is keeping the IME open, the
647        // value should never be stale.
648        String supportedLocalesString = VoiceProxy.getSupportedLocalesString(
649                mService.getContentResolver());
650        List<String> voiceInputSupportedLocales = Arrays.asList(
651                supportedLocalesString.split("\\s+"));
652        return voiceInputSupportedLocales.contains(locale);
653    }
654
655    private void changeToNextSubtype() {
656        final InputMethodSubtypeCompatWrapper subtype =
657                mLanguageBarInfo.getNextKeyboardSubtype();
658        switchToTargetIME(mInputMethodId, subtype);
659    }
660
661    private void changeToPreviousSubtype() {
662        final InputMethodSubtypeCompatWrapper subtype =
663                mLanguageBarInfo.getPreviousKeyboardSubtype();
664        switchToTargetIME(mInputMethodId, subtype);
665    }
666
667    public void toggleLanguage(boolean next) {
668        if (next) {
669            changeToNextSubtype();
670        } else {
671            changeToPreviousSubtype();
672        }
673    }
674}
675