RichInputMethodManager.java revision 498dbfbd9dcd9a03b91b6efe4d0e5b3afb1df078
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;
20import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
21
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.inputmethodservice.InputMethodService;
25import android.os.AsyncTask;
26import android.os.Build;
27import android.os.IBinder;
28import android.preference.PreferenceManager;
29import android.util.Log;
30import android.view.inputmethod.InputMethodInfo;
31import android.view.inputmethod.InputMethodManager;
32import android.view.inputmethod.InputMethodSubtype;
33
34import com.android.inputmethod.annotations.UsedForTesting;
35import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
36import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
37import com.android.inputmethod.latin.settings.Settings;
38import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
39import com.android.inputmethod.latin.utils.NetworkConnectivityUtils;
40import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
41
42import java.util.Collections;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Locale;
47import java.util.Map;
48import java.util.Set;
49
50import javax.annotation.Nonnull;
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    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
73            mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>();
74    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
75            mSubtypeListCacheWithoutImplicitlySelectedSubtypes = new HashMap<>();
76
77    private static final int INDEX_NOT_FOUND = -1;
78
79    public static RichInputMethodManager getInstance() {
80        sInstance.checkInitialized();
81        return sInstance;
82    }
83
84    public static void init(final Context context) {
85        sInstance.initInternal(context);
86    }
87
88    private boolean isInitialized() {
89        return mImmWrapper != null;
90    }
91
92    private void checkInitialized() {
93        if (!isInitialized()) {
94            throw new RuntimeException(TAG + " is used before initialization");
95        }
96    }
97
98    private void initInternal(final Context context) {
99        if (isInitialized()) {
100            return;
101        }
102        mImmWrapper = new InputMethodManagerCompatWrapper(context);
103        mContext = context;
104        mInputMethodInfoCache = new InputMethodInfoCache(
105                mImmWrapper.mImm, context.getPackageName());
106
107        // Initialize additional subtypes.
108        SubtypeLocaleUtils.init(context);
109        final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes();
110        mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
111                getInputMethodIdOfThisIme(), additionalSubtypes);
112
113        // Initialize the current input method subtype and the shortcut IME.
114        refreshSubtypeCaches();
115    }
116
117    public InputMethodSubtype[] getAdditionalSubtypes() {
118        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
119        final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
120                prefs, mContext.getResources());
121        return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
122    }
123
124    public InputMethodManager getInputMethodManager() {
125        checkInitialized();
126        return mImmWrapper.mImm;
127    }
128
129    public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
130            boolean allowsImplicitlySelectedSubtypes) {
131        return getEnabledInputMethodSubtypeList(
132                getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes);
133    }
134
135    public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
136        if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
137            return true;
138        }
139        // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
140        // because the current device is running ICS or previous and lacks the API.
141        if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) {
142            return true;
143        }
144        return switchToNextInputMethodAndSubtype(token);
145    }
146
147    private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
148            final boolean onlyCurrentIme) {
149        final InputMethodManager imm = mImmWrapper.mImm;
150        final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
151        final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
152                true /* allowsImplicitlySelectedSubtypes */);
153        final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
154        if (currentIndex == INDEX_NOT_FOUND) {
155            Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
156                    + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
157            return false;
158        }
159        final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
160        if (nextIndex <= currentIndex && !onlyCurrentIme) {
161            // The current subtype is the last or only enabled one and it needs to switch to
162            // next IME.
163            return false;
164        }
165        final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
166        setInputMethodAndSubtype(token, nextSubtype);
167        return true;
168    }
169
170    private boolean switchToNextInputMethodAndSubtype(final IBinder token) {
171        final InputMethodManager imm = mImmWrapper.mImm;
172        final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
173        final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis);
174        if (currentIndex == INDEX_NOT_FOUND) {
175            Log.w(TAG, "Can't find current IME in enabled IMEs: IME package="
176                    + getInputMethodInfoOfThisIme().getPackageName());
177            return false;
178        }
179        final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
180        final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
181                true /* allowsImplicitlySelectedSubtypes */);
182        if (enabledSubtypes.isEmpty()) {
183            // The next IME has no subtype.
184            imm.setInputMethod(token, nextImi.getId());
185            return true;
186        }
187        final InputMethodSubtype firstSubtype = enabledSubtypes.get(0);
188        imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype);
189        return true;
190    }
191
192    private static int getImiIndexInList(final InputMethodInfo inputMethodInfo,
193            final List<InputMethodInfo> imiList) {
194        final int count = imiList.size();
195        for (int index = 0; index < count; index++) {
196            final InputMethodInfo imi = imiList.get(index);
197            if (imi.equals(inputMethodInfo)) {
198                return index;
199            }
200        }
201        return INDEX_NOT_FOUND;
202    }
203
204    // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}.
205    private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex,
206            final List<InputMethodInfo> imiList) {
207        final int count = imiList.size();
208        for (int i = 1; i < count; i++) {
209            final int nextIndex = (currentIndex + i) % count;
210            final InputMethodInfo nextImi = imiList.get(nextIndex);
211            if (!isAuxiliaryIme(nextImi)) {
212                return nextImi;
213            }
214        }
215        return imiList.get(currentIndex);
216    }
217
218    // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined.
219    private static boolean isAuxiliaryIme(final InputMethodInfo imi) {
220        final int count = imi.getSubtypeCount();
221        if (count == 0) {
222            return false;
223        }
224        for (int index = 0; index < count; index++) {
225            final InputMethodSubtype subtype = imi.getSubtypeAt(index);
226            if (!subtype.isAuxiliary()) {
227                return false;
228            }
229        }
230        return true;
231    }
232
233    private static class InputMethodInfoCache {
234        private final InputMethodManager mImm;
235        private final String mImePackageName;
236
237        private InputMethodInfo mCachedValue;
238
239        public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) {
240            mImm = imm;
241            mImePackageName = imePackageName;
242        }
243
244        public synchronized InputMethodInfo get() {
245            if (mCachedValue != null) {
246                return mCachedValue;
247            }
248            for (final InputMethodInfo imi : mImm.getInputMethodList()) {
249                if (imi.getPackageName().equals(mImePackageName)) {
250                    mCachedValue = imi;
251                    return imi;
252                }
253            }
254            throw new RuntimeException("Input method id for " + mImePackageName + " not found.");
255        }
256
257        public synchronized void clear() {
258            mCachedValue = null;
259        }
260    }
261
262    public InputMethodInfo getInputMethodInfoOfThisIme() {
263        return mInputMethodInfoCache.get();
264    }
265
266    public String getInputMethodIdOfThisIme() {
267        return getInputMethodInfoOfThisIme().getId();
268    }
269
270    public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
271        return checkIfSubtypeBelongsToImeAndEnabled(getInputMethodInfoOfThisIme(), subtype);
272    }
273
274    public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
275            final InputMethodSubtype subtype) {
276        final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
277        final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
278                subtype, getMyEnabledInputMethodSubtypeList(
279                        false /* allowsImplicitlySelectedSubtypes */));
280        return subtypeEnabled && !subtypeExplicitlyEnabled;
281    }
282
283    public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
284            final InputMethodSubtype subtype) {
285        return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
286                true /* allowsImplicitlySelectedSubtypes */));
287    }
288
289    private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
290            final List<InputMethodSubtype> subtypes) {
291        return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
292    }
293
294    private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
295            final List<InputMethodSubtype> subtypes) {
296        final int count = subtypes.size();
297        for (int index = 0; index < count; index++) {
298            final InputMethodSubtype ims = subtypes.get(index);
299            if (ims.equals(subtype)) {
300                return index;
301            }
302        }
303        return INDEX_NOT_FOUND;
304    }
305
306    public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
307        return getSubtypeIndexInIme(subtype, getInputMethodInfoOfThisIme()) != INDEX_NOT_FOUND;
308    }
309
310    private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
311            final InputMethodInfo imi) {
312        final int count = imi.getSubtypeCount();
313        for (int index = 0; index < count; index++) {
314            final InputMethodSubtype ims = imi.getSubtypeAt(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 = new RichInputMethodSubtype(subtype);
335    }
336
337    @Nonnull
338    public Locale[] getCurrentSubtypeLocales() {
339        if (null != sForcedSubtypeForTesting) {
340            return sForcedSubtypeForTesting.getLocales();
341        }
342        return getCurrentSubtype().getLocales();
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                continue;
399            }
400        }
401
402        if (filteredImisCount > 1) {
403            return true;
404        }
405        final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true);
406        int keyboardCount = 0;
407        // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
408        // both explicitly and implicitly enabled input method subtype.
409        // (The current IME should be LatinIME.)
410        for (InputMethodSubtype subtype : subtypes) {
411            if (KEYBOARD_MODE.equals(subtype.getMode())) {
412                ++keyboardCount;
413            }
414        }
415        return keyboardCount > 1;
416    }
417
418    public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString,
419            final String keyboardLayoutSetName) {
420        final InputMethodInfo myImi = getInputMethodInfoOfThisIme();
421        final int count = myImi.getSubtypeCount();
422        for (int i = 0; i < count; i++) {
423            final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
424            final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
425            if (localeString.equals(subtype.getLocale())
426                    && keyboardLayoutSetName.equals(layoutName)) {
427                return subtype;
428            }
429        }
430        return null;
431    }
432
433    public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
434        mImmWrapper.mImm.setInputMethodAndSubtype(
435                token, getInputMethodIdOfThisIme(), subtype);
436    }
437
438    public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
439        mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
440                getInputMethodIdOfThisIme(), subtypes);
441        // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of
442        // subtypes again next time.
443        refreshSubtypeCaches();
444    }
445
446    private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
447            final boolean allowsImplicitlySelectedSubtypes) {
448        final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
449                allowsImplicitlySelectedSubtypes
450                ? mSubtypeListCacheWithImplicitlySelectedSubtypes
451                : mSubtypeListCacheWithoutImplicitlySelectedSubtypes;
452        final List<InputMethodSubtype> cachedList = cache.get(imi);
453        if (null != cachedList) return cachedList;
454        final List<InputMethodSubtype> result = mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
455                imi, allowsImplicitlySelectedSubtypes);
456        cache.put(imi, result);
457        return result;
458    }
459
460    public void refreshSubtypeCaches() {
461        mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
462        mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
463        mInputMethodInfoCache.clear();
464        updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype());
465        updateShortcutIme();
466    }
467
468    public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
469            boolean defaultValue) {
470        // Use the default value instead on Jelly Bean MR2 and previous where
471        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available
472        // and on KitKat where the API is still just a stub to return true always.
473        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
474            return defaultValue;
475        }
476        return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
477    }
478
479    public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
480        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
481        final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
482        final InputMethodManager inputMethodManager = getInputMethodManager();
483        final List<InputMethodInfo> enabledInputMethodInfoList =
484                inputMethodManager.getEnabledInputMethodList();
485        for (final InputMethodInfo info : enabledInputMethodInfoList) {
486            final List<InputMethodSubtype> enabledSubtypes =
487                    inputMethodManager.getEnabledInputMethodSubtypeList(
488                            info, true /* allowsImplicitlySelectedSubtypes */);
489            if (enabledSubtypes.isEmpty()) {
490                // An IME with no subtypes is found.
491                return false;
492            }
493            enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
494        }
495        for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
496            if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
497                    && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
498                return false;
499            }
500        }
501        return true;
502    }
503
504    private void updateCurrentSubtype(@Nonnull final InputMethodSubtype subtype) {
505        mCurrentRichInputMethodSubtype = new RichInputMethodSubtype(subtype);
506    }
507
508    private void updateShortcutIme() {
509        if (DEBUG) {
510            Log.d(TAG, "Update shortcut IME from : "
511                    + (mShortcutInputMethodInfo == null
512                            ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
513                    + (mShortcutSubtype == null ? "<null>" : (
514                            mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
515        }
516        // TODO: Update an icon for shortcut IME
517        final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
518                getInputMethodManager().getShortcutInputMethodsAndSubtypes();
519        mShortcutInputMethodInfo = null;
520        mShortcutSubtype = null;
521        for (final InputMethodInfo imi : shortcuts.keySet()) {
522            final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
523            // TODO: Returns the first found IMI for now. Should handle all shortcuts as
524            // appropriate.
525            mShortcutInputMethodInfo = imi;
526            // TODO: Pick up the first found subtype for now. Should handle all subtypes
527            // as appropriate.
528            mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
529            break;
530        }
531        if (DEBUG) {
532            Log.d(TAG, "Update shortcut IME to : "
533                    + (mShortcutInputMethodInfo == null
534                            ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
535                    + (mShortcutSubtype == null ? "<null>" : (
536                            mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
537        }
538    }
539
540    public void switchToShortcutIme(final InputMethodService context) {
541        if (mShortcutInputMethodInfo == null) {
542            return;
543        }
544
545        final String imiId = mShortcutInputMethodInfo.getId();
546        switchToTargetIME(imiId, mShortcutSubtype, context);
547    }
548
549    private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
550            final InputMethodService context) {
551        final IBinder token = context.getWindow().getWindow().getAttributes().token;
552        if (token == null) {
553            return;
554        }
555        final InputMethodManager imm = getInputMethodManager();
556        new AsyncTask<Void, Void, Void>() {
557            @Override
558            protected Void doInBackground(Void... params) {
559                imm.setInputMethodAndSubtype(token, imiId, subtype);
560                return null;
561            }
562        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
563    }
564
565    public boolean isShortcutImeEnabled() {
566        if (mShortcutInputMethodInfo == null) {
567            return false;
568        }
569        if (mShortcutSubtype == null) {
570            return true;
571        }
572        return checkIfSubtypeBelongsToImeAndEnabled(mShortcutInputMethodInfo, mShortcutSubtype);
573    }
574
575    public boolean isShortcutImeReady() {
576        if (mShortcutInputMethodInfo == null) {
577            return false;
578        }
579        if (mShortcutSubtype == null) {
580            return true;
581        }
582        if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
583            return NetworkConnectivityUtils.isNetworkConnected();
584        }
585        return true;
586    }
587}
588