Enable VoiceInput even if there is no shortcut subtype supported

Change-Id: I1d455348f56d73ecb942f22c2bbd03f240b489a6
main
satok 2011-03-28 18:45:49 -07:00
parent a1f9cdd6f5
commit 7429009838
6 changed files with 165 additions and 11 deletions

View File

@ -16,8 +16,13 @@
package com.android.inputmethod.compat; 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.content.Context;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@ -27,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
// TODO: Override this class with the concrete implementation if we need to take care of the // 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 = private static final InputMethodManagerCompatWrapper sInstance =
new InputMethodManagerCompatWrapper(); 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 InputMethodManager mImm;
private String mLatinImePackageName;
private InputMethodManagerCompatWrapper() { private InputMethodManagerCompatWrapper() {
} }
@ -64,28 +78,82 @@ public class InputMethodManagerCompatWrapper {
private synchronized void init(Context context) { private synchronized void init(Context context) {
mImm = (InputMethodManager) context.getSystemService( mImm = (InputMethodManager) context.getSystemService(
Context.INPUT_METHOD_SERVICE); Context.INPUT_METHOD_SERVICE);
if (context instanceof LatinIME) {
mLatinImePackageName = context.getPackageName();
}
} }
public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() { public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
return new InputMethodSubtypeCompatWrapper( Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype);
CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype)); return new InputMethodSubtypeCompatWrapper(o);
} }
public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList( public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) { InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList, Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
(imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes); (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
// Returns an empty list if (retval == null || !(retval instanceof List) || ((List<?>)retval).isEmpty()) {
if (retval == null) if (!ALLOW_DUMMY_SUBTYPE) {
return Collections.emptyList(); // Returns an empty list
return Collections.emptyList();
}
// Creates dummy subtypes
List<InputMethodSubtypeCompatWrapper> subtypeList =
new ArrayList<InputMethodSubtypeCompatWrapper>();
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); 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<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> public Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
getShortcutInputMethodsAndSubtypes() { getShortcutInputMethodsAndSubtypes() {
Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes); Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes);
// Returns an empty map if (retval == null || !(retval instanceof Map) || ((Map<?, ?>)retval).isEmpty()) {
if (!(retval instanceof Map)) return Collections.emptyMap(); 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<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>
shortcutMap =
new HashMap<InputMethodInfoCompatWrapper,
List<InputMethodSubtypeCompatWrapper>>();
List<InputMethodSubtypeCompatWrapper> subtypeList =
new ArrayList<InputMethodSubtypeCompatWrapper>();
subtypeList.add(voiceSubtype);
shortcutMap.put(imi, subtypeList);
return shortcutMap;
} else {
return Collections.emptyMap();
}
}
Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap = Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap =
new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>(); new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
final Map<?, ?> retvalMap = (Map<?, ?>)retval; final Map<?, ?> retvalMap = (Map<?, ?>)retval;
@ -107,6 +175,9 @@ public class InputMethodManagerCompatWrapper {
} }
public boolean switchToLastInputMethod(IBinder token) { public boolean switchToLastInputMethod(IBinder token) {
if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
return true;
}
return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token); return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
} }

View File

@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.lang.reflect.Method; 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 // TODO: Override this class with the concrete implementation if we need to take care of the
// performance. // performance.
@ -48,35 +49,65 @@ public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper
private static final Method METHOD_getExtraValueOf = private static final Method METHOD_getExtraValueOf =
CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class); 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) { public InputMethodSubtypeCompatWrapper(Object subtype) {
super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype)) super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
? subtype : null); ? subtype : null);
if (DBG) { if (DBG) {
Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper"); 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() { public int getNameResId() {
if (mObj == null) return mDummyNameResId;
return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId); return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId);
} }
public int getIconResId() { public int getIconResId() {
if (mObj == null) return mDummyIconResId;
return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId); return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId);
} }
public String getLocale() { public String getLocale() {
if (mObj == null) return mDummyLocale;
final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale); final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale);
if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE; if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE;
return s; return s;
} }
public String getMode() { public String getMode() {
if (mObj == null) return mDummyMode;
String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode); String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode);
if (TextUtils.isEmpty(s)) return DEFAULT_MODE; if (TextUtils.isEmpty(s)) return DEFAULT_MODE;
return s; return s;
} }
public String getExtraValue() { public String getExtraValue() {
if (mObj == null) return mDummyExtraValues;
return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue); return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue);
} }
@ -92,10 +123,32 @@ public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof InputMethodSubtypeCompatWrapper) { if (o instanceof InputMethodSubtypeCompatWrapper) {
InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o; 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()); return mObj.equals(subtype.getOriginalObject());
} else { } else {
return mObj.equals(o); 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 });
}
} }

View File

@ -82,6 +82,8 @@ public class VoiceProxy implements VoiceInput.UiListener {
private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
"has_used_voice_input_unsupported_locale"; "has_used_voice_input_unsupported_locale";
private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; 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 String TAG = VoiceProxy.class.getSimpleName();
private static final boolean DEBUG = LatinImeLogger.sDBG; private static final boolean DEBUG = LatinImeLogger.sDBG;
@ -99,6 +101,7 @@ public class VoiceProxy implements VoiceInput.UiListener {
private boolean mVoiceButtonOnPrimary; private boolean mVoiceButtonOnPrimary;
private boolean mVoiceInputHighlighted; private boolean mVoiceInputHighlighted;
private int mMinimumVoiceRecognitionViewHeightPixel;
private InputMethodManagerCompatWrapper mImm; private InputMethodManagerCompatWrapper mImm;
private LatinIME mService; private LatinIME mService;
private AlertDialog mVoiceWarningDialog; private AlertDialog mVoiceWarningDialog;
@ -107,6 +110,7 @@ public class VoiceProxy implements VoiceInput.UiListener {
private Hints mHints; private Hints mHints;
private UIHandler mHandler; private UIHandler mHandler;
private SubtypeSwitcher mSubtypeSwitcher; private SubtypeSwitcher mSubtypeSwitcher;
// For each word, a list of potential replacements, usually from voice. // For each word, a list of potential replacements, usually from voice.
private final Map<String, List<CharSequence>> mWordToSuggestions = private final Map<String, List<CharSequence>> mWordToSuggestions =
new HashMap<String, List<CharSequence>>(); new HashMap<String, List<CharSequence>>();
@ -123,6 +127,8 @@ public class VoiceProxy implements VoiceInput.UiListener {
private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
mService = service; mService = service;
mHandler = h; mHandler = h;
mMinimumVoiceRecognitionViewHeightPixel = Utils.dipToPixel(
Utils.getDipScale(service), RECOGNITIONVIEW_MINIMUM_HEIGHT_DIP);
mImm = InputMethodManagerCompatWrapper.getInstance(service); mImm = InputMethodManagerCompatWrapper.getInstance(service);
mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mSubtypeSwitcher = SubtypeSwitcher.getInstance();
if (VOICE_INSTALLED) { if (VOICE_INSTALLED) {
@ -542,7 +548,11 @@ public class VoiceProxy implements VoiceInput.UiListener {
mService.getResources().getDisplayMetrics().heightPixels; mService.getResources().getDisplayMetrics().heightPixels;
final int currentHeight = popupLayout.getLayoutParams().height; final int currentHeight = popupLayout.getLayoutParams().height;
final int keyboardHeight = keyboardView.getHeight(); 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)) { > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) {
popupLayout.getLayoutParams().height = keyboardHeight; popupLayout.getLayoutParams().height = keyboardHeight;
} }

View File

@ -50,7 +50,6 @@ import java.util.Locale;
* plays beeps, shows errors, etc. * plays beeps, shows errors, etc.
*/ */
public class RecognitionView { public class RecognitionView {
@SuppressWarnings("unused")
private static final String TAG = "RecognitionView"; private static final String TAG = "RecognitionView";
private Handler mUiHandler; // Reference to UI thread private Handler mUiHandler; // Reference to UI thread

View File

@ -210,7 +210,7 @@ public class SubtypeSwitcher {
final String newLocale; final String newLocale;
final String newMode; final String newMode;
final String oldMode = getCurrentSubtypeMode(); 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, // Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
// fallback to the default locale. // fallback to the default locale.
Log.w(TAG, "Couldn't get the current subtype."); Log.w(TAG, "Couldn't get the current subtype.");
@ -539,6 +539,11 @@ public class SubtypeSwitcher {
return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode()); return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
} }
public boolean isDummyVoiceMode() {
return mCurrentSubtype != null && mCurrentSubtype.getOriginalObject() == null
&& VOICE_MODE.equals(getCurrentSubtypeMode());
}
private void triggerVoiceIME() { private void triggerVoiceIME() {
if (!mService.isInputViewShown()) return; if (!mService.isInputViewShown()) return;
VoiceProxy.getInstance().startListening(false, VoiceProxy.getInstance().startListening(false,

View File

@ -21,6 +21,7 @@ import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardId;
import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -110,9 +111,14 @@ public class Utils {
} }
public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) { 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()) { for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
if (imi.getPackageName().equals(packageName)) if (imi.getPackageName().equals(packageName))
return imi.getId(); return imi;
} }
throw new RuntimeException("Can not find input method id for " + packageName); throw new RuntimeException("Can not find input method id for " + packageName);
} }
@ -601,4 +607,14 @@ public class Utils {
} }
return true; 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);
}
} }