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