Home | History | Annotate | Download | only in latin
      1 /*
      2  * Copyright (C) 2012 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 static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
     20 
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.inputmethodservice.InputMethodService;
     24 import android.os.AsyncTask;
     25 import android.os.Build;
     26 import android.os.IBinder;
     27 import android.preference.PreferenceManager;
     28 import android.util.Log;
     29 import android.view.inputmethod.InputMethodInfo;
     30 import android.view.inputmethod.InputMethodManager;
     31 import android.view.inputmethod.InputMethodSubtype;
     32 
     33 import com.android.inputmethod.annotations.UsedForTesting;
     34 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
     35 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
     36 import com.android.inputmethod.latin.settings.Settings;
     37 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
     38 import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
     39 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     40 
     41 import java.util.Collections;
     42 import java.util.HashMap;
     43 import java.util.HashSet;
     44 import java.util.List;
     45 import java.util.Locale;
     46 import java.util.Map;
     47 import java.util.Set;
     48 
     49 import javax.annotation.Nonnull;
     50 import javax.annotation.Nullable;
     51 
     52 /**
     53  * Enrichment class for InputMethodManager to simplify interaction and add functionality.
     54  */
     55 // non final for easy mocking.
     56 public class RichInputMethodManager {
     57     private static final String TAG = RichInputMethodManager.class.getSimpleName();
     58     private static final boolean DEBUG = false;
     59 
     60     private RichInputMethodManager() {
     61         // This utility class is not publicly instantiable.
     62     }
     63 
     64     private static final RichInputMethodManager sInstance = new RichInputMethodManager();
     65 
     66     private Context mContext;
     67     private InputMethodManagerCompatWrapper mImmWrapper;
     68     private InputMethodInfoCache mInputMethodInfoCache;
     69     private RichInputMethodSubtype mCurrentRichInputMethodSubtype;
     70     private InputMethodInfo mShortcutInputMethodInfo;
     71     private InputMethodSubtype mShortcutSubtype;
     72 
     73     private static final int INDEX_NOT_FOUND = -1;
     74 
     75     public static RichInputMethodManager getInstance() {
     76         sInstance.checkInitialized();
     77         return sInstance;
     78     }
     79 
     80     public static void init(final Context context) {
     81         sInstance.initInternal(context);
     82     }
     83 
     84     private boolean isInitialized() {
     85         return mImmWrapper != null;
     86     }
     87 
     88     private void checkInitialized() {
     89         if (!isInitialized()) {
     90             throw new RuntimeException(TAG + " is used before initialization");
     91         }
     92     }
     93 
     94     private void initInternal(final Context context) {
     95         if (isInitialized()) {
     96             return;
     97         }
     98         mImmWrapper = new InputMethodManagerCompatWrapper(context);
     99         mContext = context;
    100         mInputMethodInfoCache = new InputMethodInfoCache(
    101                 mImmWrapper.mImm, context.getPackageName());
    102 
    103         // Initialize additional subtypes.
    104         SubtypeLocaleUtils.init(context);
    105         final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes();
    106         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
    107                 getInputMethodIdOfThisIme(), additionalSubtypes);
    108 
    109         // Initialize the current input method subtype and the shortcut IME.
    110         refreshSubtypeCaches();
    111     }
    112 
    113     public InputMethodSubtype[] getAdditionalSubtypes() {
    114         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
    115         final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
    116                 prefs, mContext.getResources());
    117         return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
    118     }
    119 
    120     public InputMethodManager getInputMethodManager() {
    121         checkInitialized();
    122         return mImmWrapper.mImm;
    123     }
    124 
    125     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
    126             boolean allowsImplicitlySelectedSubtypes) {
    127         return getEnabledInputMethodSubtypeList(
    128                 getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes);
    129     }
    130 
    131     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
    132         if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
    133             return true;
    134         }
    135         // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
    136         // because the current device is running ICS or previous and lacks the API.
    137         if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) {
    138             return true;
    139         }
    140         return switchToNextInputMethodAndSubtype(token);
    141     }
    142 
    143     private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
    144             final boolean onlyCurrentIme) {
    145         final InputMethodManager imm = mImmWrapper.mImm;
    146         final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
    147         final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
    148                 true /* allowsImplicitlySelectedSubtypes */);
    149         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
    150         if (currentIndex == INDEX_NOT_FOUND) {
    151             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
    152                     + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
    153             return false;
    154         }
    155         final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
    156         if (nextIndex <= currentIndex && !onlyCurrentIme) {
    157             // The current subtype is the last or only enabled one and it needs to switch to
    158             // next IME.
    159             return false;
    160         }
    161         final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
    162         setInputMethodAndSubtype(token, nextSubtype);
    163         return true;
    164     }
    165 
    166     private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
    167         final InputMethodManager imm = mImmWrapper.mImm;
    168         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    169         final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis);
    170         if (currentIndex == INDEX_NOT_FOUND) {
    171             Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
    172                     + getInputMethodInfoOfThisIme().getPackageName());
    173             return false;
    174         }
    175         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
    176         final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
    177                 true /* allowsImplicitlySelectedSubtypes */);
    178         if (enabledSubtypes.isEmpty()) {
    179             // The next IME has no subtype.
    180             imm.setInputMethod(token, nextImi.getId());
    181             return true;
    182         }
    183         final InputMethodSubtype firstSubtype = enabledSubtypes.get(0);
    184         imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype);
    185         return true;
    186     }
    187 
    188     private static int getImiIndexInList(final InputMethodInfo inputMethodInfo,
    189             final List<InputMethodInfo> imiList) {
    190         final int count = imiList.size();
    191         for (int index = 0; index < count; index++) {
    192             final InputMethodInfo imi = imiList.get(index);
    193             if (imi.equals(inputMethodInfo)) {
    194                 return index;
    195             }
    196         }
    197         return INDEX_NOT_FOUND;
    198     }
    199 
    200     // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}.
    201     private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex,
    202             final List<InputMethodInfo> imiList) {
    203         final int count = imiList.size();
    204         for (int i = 1; i < count; i++) {
    205             final int nextIndex = (currentIndex + i) % count;
    206             final InputMethodInfo nextImi = imiList.get(nextIndex);
    207             if (!isAuxiliaryIme(nextImi)) {
    208                 return nextImi;
    209             }
    210         }
    211         return imiList.get(currentIndex);
    212     }
    213 
    214     // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined.
    215     private static boolean isAuxiliaryIme(final InputMethodInfo imi) {
    216         final int count = imi.getSubtypeCount();
    217         if (count == 0) {
    218             return false;
    219         }
    220         for (int index = 0; index < count; index++) {
    221             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
    222             if (!subtype.isAuxiliary()) {
    223                 return false;
    224             }
    225         }
    226         return true;
    227     }
    228 
    229     private static class InputMethodInfoCache {
    230         private final InputMethodManager mImm;
    231         private final String mImePackageName;
    232 
    233         private InputMethodInfo mCachedThisImeInfo;
    234         private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
    235                 mCachedSubtypeListWithImplicitlySelected;
    236         private final HashMap<InputMethodInfo, List<InputMethodSubtype>>
    237                 mCachedSubtypeListOnlyExplicitlySelected;
    238 
    239         public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
    240             mImm = imm;
    241             mImePackageName = imePackageName;
    242             mCachedSubtypeListWithImplicitlySelected = new HashMap<>();
    243             mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>();
    244         }
    245 
    246         public synchronized InputMethodInfo getInputMethodOfThisIme() {
    247             if (mCachedThisImeInfo != null) {
    248                 return mCachedThisImeInfo;
    249             }
    250             for (final InputMethodInfo imi : mImm.getInputMethodList()) {
    251                 if (imi.getPackageName().equals(mImePackageName)) {
    252                     mCachedThisImeInfo = imi;
    253                     return imi;
    254                 }
    255             }
    256             throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
    257         }
    258 
    259         public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
    260                 final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) {
    261             final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
    262                     allowsImplicitlySelectedSubtypes
    263                     ? mCachedSubtypeListWithImplicitlySelected
    264                     : mCachedSubtypeListOnlyExplicitlySelected;
    265             final List<InputMethodSubtype> cachedList = cache.get(imi);
    266             if (cachedList != null) {
    267                 return cachedList;
    268             }
    269             final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList(
    270                     imi, allowsImplicitlySelectedSubtypes);
    271             cache.put(imi, result);
    272             return result;
    273         }
    274 
    275         public synchronized void clear() {
    276             mCachedThisImeInfo = null;
    277             mCachedSubtypeListWithImplicitlySelected.clear();
    278             mCachedSubtypeListOnlyExplicitlySelected.clear();
    279         }
    280     }
    281 
    282     public InputMethodInfo getInputMethodInfoOfThisIme() {
    283         return mInputMethodInfoCache.getInputMethodOfThisIme();
    284     }
    285 
    286     public String getInputMethodIdOfThisIme() {
    287         return getInputMethodInfoOfThisIme().getId();
    288     }
    289 
    290     public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
    291         return checkIfSubtypeBelongsToList(subtype,
    292                 getEnabledInputMethodSubtypeList(
    293                         getInputMethodInfoOfThisIme(),
    294                         true /* allowsImplicitlySelectedSubtypes */));
    295     }
    296 
    297     public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
    298             final InputMethodSubtype subtype) {
    299         final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
    300         final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype,
    301                 getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */));
    302         return subtypeEnabled && !subtypeExplicitlyEnabled;
    303     }
    304 
    305     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
    306             final List<InputMethodSubtype> subtypes) {
    307         return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
    308     }
    309 
    310     private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
    311             final List<InputMethodSubtype> subtypes) {
    312         final int count = subtypes.size();
    313         for (int index = 0; index < count; index++) {
    314             final InputMethodSubtype ims = subtypes.get(index);
    315             if (ims.equals(subtype)) {
    316                 return index;
    317             }
    318         }
    319         return INDEX_NOT_FOUND;
    320     }
    321 
    322     public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) {
    323         updateCurrentSubtype(newSubtype);
    324         updateShortcutIme();
    325         if (DEBUG) {
    326             Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging());
    327         }
    328     }
    329 
    330     private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
    331 
    332     @UsedForTesting
    333     static void forceSubtype(@Nonnull final InputMethodSubtype subtype) {
    334         sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
    335     }
    336 
    337     @Nonnull
    338     public Locale getCurrentSubtypeLocale() {
    339         if (null != sForcedSubtypeForTesting) {
    340             return sForcedSubtypeForTesting.getLocale();
    341         }
    342         return getCurrentSubtype().getLocale();
    343     }
    344 
    345     @Nonnull
    346     public RichInputMethodSubtype getCurrentSubtype() {
    347         if (null != sForcedSubtypeForTesting) {
    348             return sForcedSubtypeForTesting;
    349         }
    350         return mCurrentRichInputMethodSubtype;
    351     }
    352 
    353 
    354     public String getCombiningRulesExtraValueOfCurrentSubtype() {
    355         return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
    356     }
    357 
    358     public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) {
    359         final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList();
    360         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
    361     }
    362 
    363     public boolean hasMultipleEnabledSubtypesInThisIme(
    364             final boolean shouldIncludeAuxiliarySubtypes) {
    365         final List<InputMethodInfo> imiList = Collections.singletonList(
    366                 getInputMethodInfoOfThisIme());
    367         return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
    368     }
    369 
    370     private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
    371             final List<InputMethodInfo> imiList) {
    372         // Number of the filtered IMEs
    373         int filteredImisCount = 0;
    374 
    375         for (InputMethodInfo imi : imiList) {
    376             // We can return true immediately after we find two or more filtered IMEs.
    377             if (filteredImisCount > 1) return true;
    378             final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true);
    379             // IMEs that have no subtypes should be counted.
    380             if (subtypes.isEmpty()) {
    381                 ++filteredImisCount;
    382                 continue;
    383             }
    384 
    385             int auxCount = 0;
    386             for (InputMethodSubtype subtype : subtypes) {
    387                 if (subtype.isAuxiliary()) {
    388                     ++auxCount;
    389                 }
    390             }
    391             final int nonAuxCount = subtypes.size() - auxCount;
    392 
    393             // IMEs that have one or more non-auxiliary subtypes should be counted.
    394             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
    395             // subtypes should be counted as well.
    396             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
    397                 ++filteredImisCount;
    398             }
    399         }
    400 
    401         if (filteredImisCount > 1) {
    402             return true;
    403         }
    404         final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true);
    405         int keyboardCount = 0;
    406         // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
    407         // both explicitly and implicitly enabled input method subtype.
    408         // (The current IME should be LatinIME.)
    409         for (InputMethodSubtype subtype : subtypes) {
    410             if (KEYBOARD_MODE.equals(subtype.getMode())) {
    411                 ++keyboardCount;
    412             }
    413         }
    414         return keyboardCount > 1;
    415     }
    416 
    417     public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
    418             final String keyboardLayoutSetName) {
    419         final InputMethodInfo myImi = getInputMethodInfoOfThisIme();
    420         final int count = myImi.getSubtypeCount();
    421         for (int i = 0; i < count; i++) {
    422             final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
    423             final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
    424             if (localeString.equals(subtype.getLocale())
    425                     && keyboardLayoutSetName.equals(layoutName)) {
    426                 return subtype;
    427             }
    428         }
    429         return null;
    430     }
    431 
    432     public InputMethodSubtype findSubtypeByLocale(final Locale locale) {
    433         // Find the best subtype based on a straightforward matching algorithm.
    434         // TODO: Use LocaleList#getFirstMatch() instead.
    435         final List<InputMethodSubtype> subtypes =
    436                 getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */);
    437         final int count = subtypes.size();
    438         for (int i = 0; i < count; ++i) {
    439             final InputMethodSubtype subtype = subtypes.get(i);
    440             final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
    441             if (subtypeLocale.equals(locale)) {
    442                 return subtype;
    443             }
    444         }
    445         for (int i = 0; i < count; ++i) {
    446             final InputMethodSubtype subtype = subtypes.get(i);
    447             final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
    448             if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
    449                     subtypeLocale.getCountry().equals(locale.getCountry()) &&
    450                     subtypeLocale.getVariant().equals(locale.getVariant())) {
    451                 return subtype;
    452             }
    453         }
    454         for (int i = 0; i < count; ++i) {
    455             final InputMethodSubtype subtype = subtypes.get(i);
    456             final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
    457             if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
    458                     subtypeLocale.getCountry().equals(locale.getCountry())) {
    459                 return subtype;
    460             }
    461         }
    462         for (int i = 0; i < count; ++i) {
    463             final InputMethodSubtype subtype = subtypes.get(i);
    464             final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
    465             if (subtypeLocale.getLanguage().equals(locale.getLanguage())) {
    466                 return subtype;
    467             }
    468         }
    469         return null;
    470     }
    471 
    472     public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
    473         mImmWrapper.mImm.setInputMethodAndSubtype(
    474                 token, getInputMethodIdOfThisIme(), subtype);
    475     }
    476 
    477     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
    478         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
    479                 getInputMethodIdOfThisIme(), subtypes);
    480         // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of
    481         // subtypes again next time.
    482         refreshSubtypeCaches();
    483     }
    484 
    485     private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
    486             final boolean allowsImplicitlySelectedSubtypes) {
    487         return mInputMethodInfoCache.getEnabledInputMethodSubtypeList(
    488                 imi, allowsImplicitlySelectedSubtypes);
    489     }
    490 
    491     public void refreshSubtypeCaches() {
    492         mInputMethodInfoCache.clear();
    493         updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype());
    494         updateShortcutIme();
    495     }
    496 
    497     public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
    498             boolean defaultValue) {
    499         // Use the default value instead on Jelly Bean MR2 and previous where
    500         // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available
    501         // and on KitKat where the API is still just a stub to return true always.
    502         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    503             return defaultValue;
    504         }
    505         return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
    506     }
    507 
    508     public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
    509         final Locale systemLocale = mContext.getResources().getConfiguration().locale;
    510         final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
    511         final InputMethodManager inputMethodManager = getInputMethodManager();
    512         final List<InputMethodInfo> enabledInputMethodInfoList =
    513                 inputMethodManager.getEnabledInputMethodList();
    514         for (final InputMethodInfo info : enabledInputMethodInfoList) {
    515             final List<InputMethodSubtype> enabledSubtypes =
    516                     inputMethodManager.getEnabledInputMethodSubtypeList(
    517                             info, true /* allowsImplicitlySelectedSubtypes */);
    518             if (enabledSubtypes.isEmpty()) {
    519                 // An IME with no subtypes is found.
    520                 return false;
    521             }
    522             enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
    523         }
    524         for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
    525             if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
    526                     && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
    527                 return false;
    528             }
    529         }
    530         return true;
    531     }
    532 
    533     private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) {
    534         mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
    535     }
    536 
    537     private void updateShortcutIme() {
    538         if (DEBUG) {
    539             Log.d(TAG, "Update shortcut IME from : "
    540                     + (mShortcutInputMethodInfo == null
    541                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
    542                     + (mShortcutSubtype == null ? "<null>" : (
    543                             mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
    544         }
    545         final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype;
    546         final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
    547                 richSubtype.getRawSubtype());
    548         final Locale systemLocale = mContext.getResources().getConfiguration().locale;
    549         LanguageOnSpacebarUtils.onSubtypeChanged(
    550                 richSubtype, implicitlyEnabledSubtype, systemLocale);
    551         LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList(
    552                 true /* allowsImplicitlySelectedSubtypes */));
    553 
    554         // TODO: Update an icon for shortcut IME
    555         final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
    556                 getInputMethodManager().getShortcutInputMethodsAndSubtypes();
    557         mShortcutInputMethodInfo = null;
    558         mShortcutSubtype = null;
    559         for (final InputMethodInfo imi : shortcuts.keySet()) {
    560             final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
    561             // TODO: Returns the first found IMI for now. Should handle all shortcuts as
    562             // appropriate.
    563             mShortcutInputMethodInfo = imi;
    564             // TODO: Pick up the first found subtype for now. Should handle all subtypes
    565             // as appropriate.
    566             mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
    567             break;
    568         }
    569         if (DEBUG) {
    570             Log.d(TAG, "Update shortcut IME to : "
    571                     + (mShortcutInputMethodInfo == null
    572                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
    573                     + (mShortcutSubtype == null ? "<null>" : (
    574                             mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
    575         }
    576     }
    577 
    578     public void switchToShortcutIme(final InputMethodService context) {
    579         if (mShortcutInputMethodInfo == null) {
    580             return;
    581         }
    582 
    583         final String imiId = mShortcutInputMethodInfo.getId();
    584         switchToTargetIME(imiId, mShortcutSubtype, context);
    585     }
    586 
    587     private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
    588             final InputMethodService context) {
    589         final IBinder token = context.getWindow().getWindow().getAttributes().token;
    590         if (token == null) {
    591             return;
    592         }
    593         final InputMethodManager imm = getInputMethodManager();
    594         new AsyncTask<Void, Void, Void>() {
    595             @Override
    596             protected Void doInBackground(Void... params) {
    597                 imm.setInputMethodAndSubtype(token, imiId, subtype);
    598                 return null;
    599             }
    600         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    601     }
    602 
    603     public boolean isShortcutImeReady() {
    604         if (mShortcutInputMethodInfo == null) {
    605             return false;
    606         }
    607         if (mShortcutSubtype == null) {
    608             return true;
    609         }
    610         return true;
    611     }
    612 }
    613