diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index 5e66bf4d9..80586b753 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -16,8 +16,13 @@ package com.android.inputmethod.compat; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.Utils; + import android.content.Context; import android.os.IBinder; +import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -27,6 +32,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; // TODO: Override this class with the concrete implementation if we need to take care of the @@ -50,7 +56,15 @@ public class InputMethodManagerCompatWrapper { private static final InputMethodManagerCompatWrapper sInstance = new InputMethodManagerCompatWrapper(); + // For the compatibility, IMM will create dummy subtypes if subtypes are not found. + // This is required to be false if the current behavior is broken. For now, it's ok to be true. + private static final boolean ALLOW_DUMMY_SUBTYPE = true; + private static final boolean HAS_VOICE_FUNCTION = true; + private static final String VOICE_MODE = "voice"; + private static final String KEYBOARD_MODE = "keyboard"; + private InputMethodManager mImm; + private String mLatinImePackageName; private InputMethodManagerCompatWrapper() { } @@ -64,28 +78,82 @@ public class InputMethodManagerCompatWrapper { private synchronized void init(Context context) { mImm = (InputMethodManager) context.getSystemService( Context.INPUT_METHOD_SERVICE); + if (context instanceof LatinIME) { + mLatinImePackageName = context.getPackageName(); + } } public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() { - return new InputMethodSubtypeCompatWrapper( - CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype)); + Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype); + return new InputMethodSubtypeCompatWrapper(o); } public List getEnabledInputMethodSubtypeList( InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) { Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList, (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes); - // Returns an empty list - if (retval == null) - return Collections.emptyList(); + if (retval == null || !(retval instanceof List) || ((List)retval).isEmpty()) { + if (!ALLOW_DUMMY_SUBTYPE) { + // Returns an empty list + return Collections.emptyList(); + } + // Creates dummy subtypes + List subtypeList = + new ArrayList(); + InputMethodSubtypeCompatWrapper keyboardSubtype = getLastResortSubtype(KEYBOARD_MODE); + InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE); + if (keyboardSubtype != null) { + subtypeList.add(keyboardSubtype); + } + if (voiceSubtype != null) { + subtypeList.add(voiceSubtype); + } + return subtypeList; + } return CompatUtils.copyInputMethodSubtypeListToWrapper((List)retval); } + private InputMethodInfoCompatWrapper getLatinImeInputMethodInfo() { + if (TextUtils.isEmpty(mLatinImePackageName)) + return null; + return Utils.getInputMethodInfo(this, mLatinImePackageName); + } + + @SuppressWarnings("unused") + private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) { + if (VOICE_MODE.equals(mode) && !HAS_VOICE_FUNCTION) + return null; + Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale(); + if (inputLocale == null) + return null; + return new InputMethodSubtypeCompatWrapper(0, 0, inputLocale.toString(), mode, ""); + } + public Map> getShortcutInputMethodsAndSubtypes() { Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes); - // Returns an empty map - if (!(retval instanceof Map)) return Collections.emptyMap(); + if (retval == null || !(retval instanceof Map) || ((Map)retval).isEmpty()) { + if (!ALLOW_DUMMY_SUBTYPE) { + // Returns an empty map + return Collections.emptyMap(); + } + // Creates dummy subtypes + InputMethodInfoCompatWrapper imi = getLatinImeInputMethodInfo(); + InputMethodSubtypeCompatWrapper voiceSubtype = getLastResortSubtype(VOICE_MODE); + if (imi != null && voiceSubtype != null) { + Map> + shortcutMap = + new HashMap>(); + List subtypeList = + new ArrayList(); + subtypeList.add(voiceSubtype); + shortcutMap.put(imi, subtypeList); + return shortcutMap; + } else { + return Collections.emptyMap(); + } + } Map> shortcutMap = new HashMap>(); final Map retvalMap = (Map)retval; @@ -107,6 +175,9 @@ public class InputMethodManagerCompatWrapper { } public boolean switchToLastInputMethod(IBinder token) { + if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) { + return true; + } return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token); } diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java index 3ffa81932..317b02216 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.util.Log; import java.lang.reflect.Method; +import java.util.Arrays; // TODO: Override this class with the concrete implementation if we need to take care of the // performance. @@ -48,35 +49,65 @@ public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper private static final Method METHOD_getExtraValueOf = CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class); + private final int mDummyNameResId; + private final int mDummyIconResId; + private final String mDummyLocale; + private final String mDummyMode; + private final String mDummyExtraValues; + public InputMethodSubtypeCompatWrapper(Object subtype) { super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype)) ? subtype : null); if (DBG) { Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper"); } + mDummyNameResId = 0; + mDummyIconResId = 0; + mDummyLocale = DEFAULT_LOCALE; + mDummyMode = DEFAULT_MODE; + mDummyExtraValues = ""; + } + + // Constructor for creating a dummy subtype. + public InputMethodSubtypeCompatWrapper(int nameResId, int iconResId, String locale, + String mode, String extraValues) { + super(null); + if (DBG) { + Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper"); + } + mDummyNameResId = nameResId; + mDummyIconResId = iconResId; + mDummyLocale = locale != null ? locale : ""; + mDummyMode = mode != null ? mode : ""; + mDummyExtraValues = extraValues != null ? extraValues : ""; } public int getNameResId() { + if (mObj == null) return mDummyNameResId; return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId); } public int getIconResId() { + if (mObj == null) return mDummyIconResId; return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId); } public String getLocale() { + if (mObj == null) return mDummyLocale; final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale); if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE; return s; } public String getMode() { + if (mObj == null) return mDummyMode; String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode); if (TextUtils.isEmpty(s)) return DEFAULT_MODE; return s; } public String getExtraValue() { + if (mObj == null) return mDummyExtraValues; return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue); } @@ -92,10 +123,32 @@ public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper public boolean equals(Object o) { if (o instanceof InputMethodSubtypeCompatWrapper) { InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o; + if (mObj == null) { + // easy check of dummy subtypes + return (mDummyNameResId == subtype.mDummyNameResId + && mDummyIconResId == subtype.mDummyIconResId + && mDummyLocale.equals(subtype.mDummyLocale) + && mDummyMode.equals(subtype.mDummyMode) + && mDummyExtraValues.equals(subtype.mDummyExtraValues)); + } return mObj.equals(subtype.getOriginalObject()); } else { return mObj.equals(o); } } + @Override + public int hashCode() { + if (mObj == null) { + return hashCodeInternal(mDummyNameResId, mDummyIconResId, mDummyLocale, + mDummyMode, mDummyExtraValues); + } + return mObj.hashCode(); + } + + private static int hashCodeInternal(int nameResId, int iconResId, String locale, + String mode, String extraValue) { + return Arrays + .hashCode(new Object[] { nameResId, iconResId, locale, mode, extraValue }); + } } diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java index 5fba29d90..0d0591bd0 100644 --- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java +++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java @@ -82,6 +82,8 @@ public class VoiceProxy implements VoiceInput.UiListener { private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = "has_used_voice_input_unsupported_locale"; private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; + // TODO: Adjusted on phones for now + private static final int RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP = 244; private static final String TAG = VoiceProxy.class.getSimpleName(); private static final boolean DEBUG = LatinImeLogger.sDBG; @@ -99,6 +101,7 @@ public class VoiceProxy implements VoiceInput.UiListener { private boolean mVoiceButtonOnPrimary; private boolean mVoiceInputHighlighted; + private int mMinimumVoiceRecognitionViewHeightPixel; private InputMethodManagerCompatWrapper mImm; private LatinIME mService; private AlertDialog mVoiceWarningDialog; @@ -107,6 +110,7 @@ public class VoiceProxy implements VoiceInput.UiListener { private Hints mHints; private UIHandler mHandler; private SubtypeSwitcher mSubtypeSwitcher; + // For each word, a list of potential replacements, usually from voice. private final Map> mWordToSuggestions = new HashMap>(); @@ -123,6 +127,8 @@ public class VoiceProxy implements VoiceInput.UiListener { private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { mService = service; mHandler = h; + mMinimumVoiceRecognitionViewHeightPixel = Utils.dipToPixel( + Utils.getDipScale(service), RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP); mImm = InputMethodManagerCompatWrapper.getInstance(service); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); if (VOICE_INSTALLED) { @@ -542,7 +548,11 @@ public class VoiceProxy implements VoiceInput.UiListener { mService.getResources().getDisplayMetrics().heightPixels; final int currentHeight = popupLayout.getLayoutParams().height; final int keyboardHeight = keyboardView.getHeight(); - if (keyboardHeight > currentHeight || keyboardHeight + if (mMinimumVoiceRecognitionViewHeightPixel > keyboardHeight + || mMinimumVoiceRecognitionViewHeightPixel > currentHeight) { + popupLayout.getLayoutParams().height = + mMinimumVoiceRecognitionViewHeightPixel; + } else if (keyboardHeight > currentHeight || keyboardHeight > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) { popupLayout.getLayoutParams().height = keyboardHeight; } diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java index 52c73ce90..b57c16f40 100644 --- a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java +++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java @@ -50,7 +50,6 @@ import java.util.Locale; * plays beeps, shows errors, etc. */ public class RecognitionView { - @SuppressWarnings("unused") private static final String TAG = "RecognitionView"; private Handler mUiHandler; // Reference to UI thread diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 10a3369ac..053e2abe4 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -210,7 +210,7 @@ public class SubtypeSwitcher { final String newLocale; final String newMode; final String oldMode = getCurrentSubtypeMode(); - if (newSubtype == null || !newSubtype.hasOriginalObject()) { + if (newSubtype == null) { // Normally, newSubtype shouldn't be null. But just in case newSubtype was null, // fallback to the default locale. Log.w(TAG, "Couldn't get the current subtype."); @@ -539,6 +539,11 @@ public class SubtypeSwitcher { return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode()); } + public boolean isDummyVoiceMode() { + return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null + && VOICE_MODE.equals(getCurrentSubtypeMode()); + } + private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; VoiceProxy.getInstance().startListening(false, diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 35b2b123c..3e092d938 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -21,6 +21,7 @@ import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.keyboard.KeyboardId; +import android.content.Context; import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; @@ -110,9 +111,14 @@ public class Utils { } public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) { + return getInputMethodInfo(imm, packageName).getId(); + } + + public static InputMethodInfoCompatWrapper getInputMethodInfo( + InputMethodManagerCompatWrapper imm, String packageName) { for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) { if (imi.getPackageName().equals(packageName)) - return imi.getId(); + return imi; } throw new RuntimeException("Can not find input method id for " + packageName); } @@ -601,4 +607,14 @@ public class Utils { } return true; } + + public static float getDipScale(Context context) { + final float scale = context.getResources().getDisplayMetrics().density; + return scale; + } + + /** Convert pixel to DIP */ + public static int dipToPixel(float scale, int dip) { + return (int) ((float) dip * scale + 0.5); + } }