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
17package com.android.inputmethod.latin;
18
19import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
20
21import android.content.Context;
22import android.content.SharedPreferences;
23import android.inputmethodservice.InputMethodService;
24import android.os.AsyncTask;
25import android.os.Build;
26import android.os.IBinder;
27import android.preference.PreferenceManager;
28import android.util.Log;
29import android.view.inputmethod.InputMethodInfo;
30import android.view.inputmethod.InputMethodManager;
31import android.view.inputmethod.InputMethodSubtype;
32
33import com.android.inputmethod.annotations.UsedForTesting;
34import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
35import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
36import com.android.inputmethod.latin.settings.Settings;
37import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
38import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
39import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
40
41import java.util.Collections;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Locale;
46import java.util.Map;
47import java.util.Set;
48
49import javax.annotation.Nonnull;
50import javax.annotation.Nullable;
51
52/**
53 * Enrichment class for InputMethodManager to simplify interaction and add functionality.
54 */
55// non final for easy mocking.
56public 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