Enable VoiceInput even if there is no shortcut subtype supported
Change-Id: I1d455348f56d73ecb942f22c2bbd03f240b489a6main
parent
a1f9cdd6f5
commit
7429009838
|
@ -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<InputMethodSubtypeCompatWrapper> 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<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);
|
||||
}
|
||||
|
||||
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>>
|
||||
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<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 =
|
||||
new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, List<CharSequence>> mWordToSuggestions =
|
||||
new HashMap<String, List<CharSequence>>();
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue