| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.inputmethod.latin; |
| |
| import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.inputmethodservice.InputMethodService; |
| import android.os.AsyncTask; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.preference.PreferenceManager; |
| import android.util.Log; |
| import android.view.inputmethod.InputMethodInfo; |
| import android.view.inputmethod.InputMethodManager; |
| import android.view.inputmethod.InputMethodSubtype; |
| |
| import com.android.inputmethod.annotations.UsedForTesting; |
| import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; |
| import com.android.inputmethod.latin.settings.Settings; |
| import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; |
| import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; |
| import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Enrichment class for InputMethodManager to simplify interaction and add functionality. |
| */ |
| // non final for easy mocking. |
| public class RichInputMethodManager { |
| private static final String TAG = RichInputMethodManager.class.getSimpleName(); |
| private static final boolean DEBUG = false; |
| |
| private RichInputMethodManager() { |
| // This utility class is not publicly instantiable. |
| } |
| |
| private static final RichInputMethodManager sInstance = new RichInputMethodManager(); |
| |
| private Context mContext; |
| private InputMethodManagerCompatWrapper mImmWrapper; |
| private InputMethodInfoCache mInputMethodInfoCache; |
| private RichInputMethodSubtype mCurrentRichInputMethodSubtype; |
| private InputMethodInfo mShortcutInputMethodInfo; |
| private InputMethodSubtype mShortcutSubtype; |
| |
| private static final int INDEX_NOT_FOUND = -1; |
| |
| public static RichInputMethodManager getInstance() { |
| sInstance.checkInitialized(); |
| return sInstance; |
| } |
| |
| public static void init(final Context context) { |
| sInstance.initInternal(context); |
| } |
| |
| private boolean isInitialized() { |
| return mImmWrapper != null; |
| } |
| |
| private void checkInitialized() { |
| if (!isInitialized()) { |
| throw new RuntimeException(TAG + " is used before initialization"); |
| } |
| } |
| |
| private void initInternal(final Context context) { |
| if (isInitialized()) { |
| return; |
| } |
| mImmWrapper = new InputMethodManagerCompatWrapper(context); |
| mContext = context; |
| mInputMethodInfoCache = new InputMethodInfoCache( |
| mImmWrapper.mImm, context.getPackageName()); |
| |
| // Initialize additional subtypes. |
| SubtypeLocaleUtils.init(context); |
| final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes(); |
| mImmWrapper.mImm.setAdditionalInputMethodSubtypes( |
| getInputMethodIdOfThisIme(), additionalSubtypes); |
| |
| // Initialize the current input method subtype and the shortcut IME. |
| refreshSubtypeCaches(); |
| } |
| |
| public InputMethodSubtype[] getAdditionalSubtypes() { |
| final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); |
| final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes( |
| prefs, mContext.getResources()); |
| return AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes); |
| } |
| |
| public InputMethodManager getInputMethodManager() { |
| checkInitialized(); |
| return mImmWrapper.mImm; |
| } |
| |
| public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList( |
| boolean allowsImplicitlySelectedSubtypes) { |
| return getEnabledInputMethodSubtypeList( |
| getInputMethodInfoOfThisIme(), allowsImplicitlySelectedSubtypes); |
| } |
| |
| public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { |
| if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) { |
| return true; |
| } |
| // Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)} |
| // because the current device is running ICS or previous and lacks the API. |
| if (switchToNextInputSubtypeInThisIme(token, onlyCurrentIme)) { |
| return true; |
| } |
| return switchToNextInputMethodAndSubtype(token); |
| } |
| |
| private boolean switchToNextInputSubtypeInThisIme(final IBinder token, |
| final boolean onlyCurrentIme) { |
| final InputMethodManager imm = mImmWrapper.mImm; |
| final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); |
| final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList( |
| true /* allowsImplicitlySelectedSubtypes */); |
| final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes); |
| if (currentIndex == INDEX_NOT_FOUND) { |
| Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype=" |
| + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype)); |
| return false; |
| } |
| final int nextIndex = (currentIndex + 1) % enabledSubtypes.size(); |
| if (nextIndex <= currentIndex && !onlyCurrentIme) { |
| // The current subtype is the last or only enabled one and it needs to switch to |
| // next IME. |
| return false; |
| } |
| final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex); |
| setInputMethodAndSubtype(token, nextSubtype); |
| return true; |
| } |
| |
| private boolean switchToNextInputMethodAndSubtype(final IBinder token) { |
| final InputMethodManager imm = mImmWrapper.mImm; |
| final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); |
| final int currentIndex = getImiIndexInList(getInputMethodInfoOfThisIme(), enabledImis); |
| if (currentIndex == INDEX_NOT_FOUND) { |
| Log.w(TAG, "Can't find current IME in enabled IMEs: IME package=" |
| + getInputMethodInfoOfThisIme().getPackageName()); |
| return false; |
| } |
| final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis); |
| final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi, |
| true /* allowsImplicitlySelectedSubtypes */); |
| if (enabledSubtypes.isEmpty()) { |
| // The next IME has no subtype. |
| imm.setInputMethod(token, nextImi.getId()); |
| return true; |
| } |
| final InputMethodSubtype firstSubtype = enabledSubtypes.get(0); |
| imm.setInputMethodAndSubtype(token, nextImi.getId(), firstSubtype); |
| return true; |
| } |
| |
| private static int getImiIndexInList(final InputMethodInfo inputMethodInfo, |
| final List<InputMethodInfo> imiList) { |
| final int count = imiList.size(); |
| for (int index = 0; index < count; index++) { |
| final InputMethodInfo imi = imiList.get(index); |
| if (imi.equals(inputMethodInfo)) { |
| return index; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| // This method mimics {@link InputMethodManager#switchToNextInputMethod(IBinder,boolean)}. |
| private static InputMethodInfo getNextNonAuxiliaryIme(final int currentIndex, |
| final List<InputMethodInfo> imiList) { |
| final int count = imiList.size(); |
| for (int i = 1; i < count; i++) { |
| final int nextIndex = (currentIndex + i) % count; |
| final InputMethodInfo nextImi = imiList.get(nextIndex); |
| if (!isAuxiliaryIme(nextImi)) { |
| return nextImi; |
| } |
| } |
| return imiList.get(currentIndex); |
| } |
| |
| // Copied from {@link InputMethodInfo}. See how auxiliary of IME is determined. |
| private static boolean isAuxiliaryIme(final InputMethodInfo imi) { |
| final int count = imi.getSubtypeCount(); |
| if (count == 0) { |
| return false; |
| } |
| for (int index = 0; index < count; index++) { |
| final InputMethodSubtype subtype = imi.getSubtypeAt(index); |
| if (!subtype.isAuxiliary()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static class InputMethodInfoCache { |
| private final InputMethodManager mImm; |
| private final String mImePackageName; |
| |
| private InputMethodInfo mCachedThisImeInfo; |
| private final HashMap<InputMethodInfo, List<InputMethodSubtype>> |
| mCachedSubtypeListWithImplicitlySelected; |
| private final HashMap<InputMethodInfo, List<InputMethodSubtype>> |
| mCachedSubtypeListOnlyExplicitlySelected; |
| |
| public InputMethodInfoCache(final InputMethodManager imm, final String imePackageName) { |
| mImm = imm; |
| mImePackageName = imePackageName; |
| mCachedSubtypeListWithImplicitlySelected = new HashMap<>(); |
| mCachedSubtypeListOnlyExplicitlySelected = new HashMap<>(); |
| } |
| |
| public synchronized InputMethodInfo getInputMethodOfThisIme() { |
| if (mCachedThisImeInfo != null) { |
| return mCachedThisImeInfo; |
| } |
| for (final InputMethodInfo imi : mImm.getInputMethodList()) { |
| if (imi.getPackageName().equals(mImePackageName)) { |
| mCachedThisImeInfo = imi; |
| return imi; |
| } |
| } |
| throw new RuntimeException("Input method id for " + mImePackageName + " not found."); |
| } |
| |
| public synchronized List<InputMethodSubtype> getEnabledInputMethodSubtypeList( |
| final InputMethodInfo imi, final boolean allowsImplicitlySelectedSubtypes) { |
| final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache = |
| allowsImplicitlySelectedSubtypes |
| ? mCachedSubtypeListWithImplicitlySelected |
| : mCachedSubtypeListOnlyExplicitlySelected; |
| final List<InputMethodSubtype> cachedList = cache.get(imi); |
| if (cachedList != null) { |
| return cachedList; |
| } |
| final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList( |
| imi, allowsImplicitlySelectedSubtypes); |
| cache.put(imi, result); |
| return result; |
| } |
| |
| public synchronized void clear() { |
| mCachedThisImeInfo = null; |
| mCachedSubtypeListWithImplicitlySelected.clear(); |
| mCachedSubtypeListOnlyExplicitlySelected.clear(); |
| } |
| } |
| |
| public InputMethodInfo getInputMethodInfoOfThisIme() { |
| return mInputMethodInfoCache.getInputMethodOfThisIme(); |
| } |
| |
| public String getInputMethodIdOfThisIme() { |
| return getInputMethodInfoOfThisIme().getId(); |
| } |
| |
| public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) { |
| return checkIfSubtypeBelongsToList(subtype, |
| getEnabledInputMethodSubtypeList( |
| getInputMethodInfoOfThisIme(), |
| true /* allowsImplicitlySelectedSubtypes */)); |
| } |
| |
| public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( |
| final InputMethodSubtype subtype) { |
| final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); |
| final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype, |
| getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */)); |
| return subtypeEnabled && !subtypeExplicitlyEnabled; |
| } |
| |
| private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, |
| final List<InputMethodSubtype> subtypes) { |
| return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND; |
| } |
| |
| private static int getSubtypeIndexInList(final InputMethodSubtype subtype, |
| final List<InputMethodSubtype> subtypes) { |
| final int count = subtypes.size(); |
| for (int index = 0; index < count; index++) { |
| final InputMethodSubtype ims = subtypes.get(index); |
| if (ims.equals(subtype)) { |
| return index; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) { |
| updateCurrentSubtype(newSubtype); |
| updateShortcutIme(); |
| if (DEBUG) { |
| Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging()); |
| } |
| } |
| |
| private static RichInputMethodSubtype sForcedSubtypeForTesting = null; |
| |
| @UsedForTesting |
| static void forceSubtype(@Nonnull final InputMethodSubtype subtype) { |
| sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); |
| } |
| |
| @Nonnull |
| public Locale getCurrentSubtypeLocale() { |
| if (null != sForcedSubtypeForTesting) { |
| return sForcedSubtypeForTesting.getLocale(); |
| } |
| return getCurrentSubtype().getLocale(); |
| } |
| |
| @Nonnull |
| public RichInputMethodSubtype getCurrentSubtype() { |
| if (null != sForcedSubtypeForTesting) { |
| return sForcedSubtypeForTesting; |
| } |
| return mCurrentRichInputMethodSubtype; |
| } |
| |
| |
| public String getCombiningRulesExtraValueOfCurrentSubtype() { |
| return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype()); |
| } |
| |
| public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { |
| final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList(); |
| return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis); |
| } |
| |
| public boolean hasMultipleEnabledSubtypesInThisIme( |
| final boolean shouldIncludeAuxiliarySubtypes) { |
| final List<InputMethodInfo> imiList = Collections.singletonList( |
| getInputMethodInfoOfThisIme()); |
| return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList); |
| } |
| |
| private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, |
| final List<InputMethodInfo> imiList) { |
| // Number of the filtered IMEs |
| int filteredImisCount = 0; |
| |
| for (InputMethodInfo imi : imiList) { |
| // We can return true immediately after we find two or more filtered IMEs. |
| if (filteredImisCount > 1) return true; |
| final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true); |
| // IMEs that have no subtypes should be counted. |
| if (subtypes.isEmpty()) { |
| ++filteredImisCount; |
| continue; |
| } |
| |
| int auxCount = 0; |
| for (InputMethodSubtype subtype : subtypes) { |
| if (subtype.isAuxiliary()) { |
| ++auxCount; |
| } |
| } |
| final int nonAuxCount = subtypes.size() - auxCount; |
| |
| // IMEs that have one or more non-auxiliary subtypes should be counted. |
| // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary |
| // subtypes should be counted as well. |
| if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { |
| ++filteredImisCount; |
| } |
| } |
| |
| if (filteredImisCount > 1) { |
| return true; |
| } |
| final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true); |
| int keyboardCount = 0; |
| // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's |
| // both explicitly and implicitly enabled input method subtype. |
| // (The current IME should be LatinIME.) |
| for (InputMethodSubtype subtype : subtypes) { |
| if (KEYBOARD_MODE.equals(subtype.getMode())) { |
| ++keyboardCount; |
| } |
| } |
| return keyboardCount > 1; |
| } |
| |
| public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, |
| final String keyboardLayoutSetName) { |
| final InputMethodInfo myImi = getInputMethodInfoOfThisIme(); |
| final int count = myImi.getSubtypeCount(); |
| for (int i = 0; i < count; i++) { |
| final InputMethodSubtype subtype = myImi.getSubtypeAt(i); |
| final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); |
| if (localeString.equals(subtype.getLocale()) |
| && keyboardLayoutSetName.equals(layoutName)) { |
| return subtype; |
| } |
| } |
| return null; |
| } |
| |
| public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { |
| mImmWrapper.mImm.setInputMethodAndSubtype( |
| token, getInputMethodIdOfThisIme(), subtype); |
| } |
| |
| public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) { |
| mImmWrapper.mImm.setAdditionalInputMethodSubtypes( |
| getInputMethodIdOfThisIme(), subtypes); |
| // Clear the cache so that we go read the {@link InputMethodInfo} of this IME and list of |
| // subtypes again next time. |
| refreshSubtypeCaches(); |
| } |
| |
| private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi, |
| final boolean allowsImplicitlySelectedSubtypes) { |
| return mInputMethodInfoCache.getEnabledInputMethodSubtypeList( |
| imi, allowsImplicitlySelectedSubtypes); |
| } |
| |
| public void refreshSubtypeCaches() { |
| mInputMethodInfoCache.clear(); |
| updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype()); |
| updateShortcutIme(); |
| } |
| |
| public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder, |
| boolean defaultValue) { |
| // Use the default value instead on Jelly Bean MR2 and previous where |
| // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available |
| // and on KitKat where the API is still just a stub to return true always. |
| if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { |
| return defaultValue; |
| } |
| return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder); |
| } |
| |
| public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { |
| final Locale systemLocale = mContext.getResources().getConfiguration().locale; |
| final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); |
| final InputMethodManager inputMethodManager = getInputMethodManager(); |
| final List<InputMethodInfo> enabledInputMethodInfoList = |
| inputMethodManager.getEnabledInputMethodList(); |
| for (final InputMethodInfo info : enabledInputMethodInfoList) { |
| final List<InputMethodSubtype> enabledSubtypes = |
| inputMethodManager.getEnabledInputMethodSubtypeList( |
| info, true /* allowsImplicitlySelectedSubtypes */); |
| if (enabledSubtypes.isEmpty()) { |
| // An IME with no subtypes is found. |
| return false; |
| } |
| enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); |
| } |
| for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { |
| if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() |
| && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) { |
| mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype); |
| } |
| |
| private void updateShortcutIme() { |
| if (DEBUG) { |
| Log.d(TAG, "Update shortcut IME from : " |
| + (mShortcutInputMethodInfo == null |
| ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " |
| + (mShortcutSubtype == null ? "<null>" : ( |
| mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); |
| } |
| final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype; |
| final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( |
| richSubtype.getRawSubtype()); |
| final Locale systemLocale = mContext.getResources().getConfiguration().locale; |
| LanguageOnSpacebarUtils.onSubtypeChanged( |
| richSubtype, implicitlyEnabledSubtype, systemLocale); |
| LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList( |
| true /* allowsImplicitlySelectedSubtypes */)); |
| |
| // TODO: Update an icon for shortcut IME |
| final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = |
| getInputMethodManager().getShortcutInputMethodsAndSubtypes(); |
| mShortcutInputMethodInfo = null; |
| mShortcutSubtype = null; |
| for (final InputMethodInfo imi : shortcuts.keySet()) { |
| final List<InputMethodSubtype> subtypes = shortcuts.get(imi); |
| // TODO: Returns the first found IMI for now. Should handle all shortcuts as |
| // appropriate. |
| mShortcutInputMethodInfo = imi; |
| // TODO: Pick up the first found subtype for now. Should handle all subtypes |
| // as appropriate. |
| mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; |
| break; |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Update shortcut IME to : " |
| + (mShortcutInputMethodInfo == null |
| ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " |
| + (mShortcutSubtype == null ? "<null>" : ( |
| mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode()))); |
| } |
| } |
| |
| public void switchToShortcutIme(final InputMethodService context) { |
| if (mShortcutInputMethodInfo == null) { |
| return; |
| } |
| |
| final String imiId = mShortcutInputMethodInfo.getId(); |
| switchToTargetIME(imiId, mShortcutSubtype, context); |
| } |
| |
| private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype, |
| final InputMethodService context) { |
| final IBinder token = context.getWindow().getWindow().getAttributes().token; |
| if (token == null) { |
| return; |
| } |
| final InputMethodManager imm = getInputMethodManager(); |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| imm.setInputMethodAndSubtype(token, imiId, subtype); |
| return null; |
| } |
| }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } |
| |
| public boolean isShortcutImeReady() { |
| if (mShortcutInputMethodInfo == null) { |
| return false; |
| } |
| if (mShortcutSubtype == null) { |
| return true; |
| } |
| return true; |
| } |
| } |