diff options
Diffstat (limited to 'java/src')
40 files changed, 1863 insertions, 406 deletions
diff --git a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java new file mode 100644 index 000000000..65949357f --- /dev/null +++ b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.util.Log; + +public abstract class AbstractCompatWrapper { + private static final String TAG = AbstractCompatWrapper.class.getSimpleName(); + protected final Object mObj; + + public AbstractCompatWrapper(Object obj) { + if (obj == null) { + Log.e(TAG, "Invalid input to AbstructCompatWrapper"); + } + mObj = obj; + } + + public Object getOriginalObject() { + return mObj; + } + + public boolean hasOriginalObject() { + return mObj != null; + } +} diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java new file mode 100644 index 000000000..f06760e8a --- /dev/null +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.content.Intent; +import android.text.TextUtils; +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class CompatUtils { + private static final String TAG = CompatUtils.class.getSimpleName(); + private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; + // TODO: Can these be constants instead of literal String constants? + private static final String INPUT_METHOD_SUBTYPE_SETTINGS = + "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; + private static final String INPUT_LANGUAGE_SELECTION = + "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; + + public static Intent getInputLanguageSelectionIntent(String inputMethodId, + int flagsForSubtypeSettings) { + final String action; + Intent intent; + if (android.os.Build.VERSION.SDK_INT + >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { + // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS + action = INPUT_METHOD_SUBTYPE_SETTINGS; + intent = new Intent(action); + if (!TextUtils.isEmpty(inputMethodId)) { + intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); + } + if (flagsForSubtypeSettings > 0) { + intent.setFlags(flagsForSubtypeSettings); + } + } else { + action = INPUT_LANGUAGE_SELECTION; + intent = new Intent(action); + } + return intent; + } + + public static Class<?> getClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + + public static Method getMethod(Class<?> targetClass, String name, + Class<?>... parameterTypes) { + if (targetClass == null || TextUtils.isEmpty(name)) return null; + try { + return targetClass.getMethod(name, parameterTypes); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchMethodException e) { + // ignore + return null; + } + } + + public static Field getField(Class<?> targetClass, String name) { + try { + return targetClass.getField(name); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchFieldException e) { + // ignore + return null; + } + } + + public static Constructor<?> getConstructor(Class<?> targetClass, Class<?>[] types) { + if (targetClass == null || types == null) return null; + try { + return targetClass.getConstructor(types); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchMethodException e) { + // ignore + return null; + } + } + + public static Object invoke( + Object receiver, Object defaultValue, Method method, Object... args) { + if (receiver == null || method == null) return defaultValue; + try { + return method.invoke(receiver, args); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception in invoke: IllegalArgumentException"); + return defaultValue; + } catch (IllegalAccessException e) { + Log.e(TAG, "Exception in invoke: IllegalAccessException"); + return defaultValue; + } catch (InvocationTargetException e) { + Log.e(TAG, "Exception in invoke: IllegalTargetException"); + return defaultValue; + } + } + + public static Object getFieldValue(Object receiver, Object defaultValue, Field field) { + if (receiver == null || field == null) return defaultValue; + try { + return field.get(receiver); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception in getFieldValue: IllegalArgumentException"); + return defaultValue; + } catch (IllegalAccessException e) { + Log.e(TAG, "Exception in getFieldValue: IllegalAccessException"); + return defaultValue; + } + } + + public static void setFieldValue(Object receiver, Field field, Object value) { + if (receiver == null || field == null) return; + try { + field.set(receiver, value); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception in setFieldValue: IllegalArgumentException"); + } catch (IllegalAccessException e) { + Log.e(TAG, "Exception in setFieldValue: IllegalAccessException"); + } + } + + public static List<InputMethodSubtypeCompatWrapper> copyInputMethodSubtypeListToWrapper( + Object listObject) { + if (!(listObject instanceof List<?>)) return null; + final List<InputMethodSubtypeCompatWrapper> subtypes = + new ArrayList<InputMethodSubtypeCompatWrapper>(); + for (Object o: (List<?>)listObject) { + subtypes.add(new InputMethodSubtypeCompatWrapper(o)); + } + return subtypes; + } +} diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java new file mode 100644 index 000000000..f6f4f7a59 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import java.lang.reflect.Field; + +public class EditorInfoCompatUtils { + private static final Field FIELD_IME_FLAG_NAVIGATE_NEXT = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT"); + private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS"); + private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_ACTION_PREVIOUS"); + private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT); + private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS); + private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS); + + public static boolean hasFlagNavigateNext(int imeOptions) { + if (OBJ_IME_FLAG_NAVIGATE_NEXT == null) + return false; + return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0; + } + + public static boolean hasFlagNavigatePrevious(int imeOptions) { + if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null) + return false; + return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0; + } + + public static void performEditorActionNext(InputConnection ic) { + ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); + } + + public static void performEditorActionPrevious(InputConnection ic) { + if (OBJ_IME_ACTION_PREVIOUS == null) + return; + ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS); + } + + public static String imeOptionsName(int imeOptions) { + if (imeOptions == -1) + return null; + final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION; + final String action; + switch (actionId) { + case EditorInfo.IME_ACTION_UNSPECIFIED: + action = "actionUnspecified"; + break; + case EditorInfo.IME_ACTION_NONE: + action = "actionNone"; + break; + case EditorInfo.IME_ACTION_GO: + action = "actionGo"; + break; + case EditorInfo.IME_ACTION_SEARCH: + action = "actionSearch"; + break; + case EditorInfo.IME_ACTION_SEND: + action = "actionSend"; + break; + case EditorInfo.IME_ACTION_DONE: + action = "actionDone"; + break; + default: { + if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) { + action = "actionPrevious"; + } else { + action = "actionUnknown(" + actionId + ")"; + } + break; + } + } + if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { + return "flagNoEnterAction|" + action; + } else { + return action; + } + } +} diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java new file mode 100644 index 000000000..b4fe32765 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.util.Log; +import android.view.inputmethod.InputConnection; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class InputConnectionCompatUtils { + private static final String TAG = InputConnectionCompatUtils.class.getSimpleName(); + private static final Class<?> CLASS_CorrectionInfo = CompatUtils + .getClass("android.view.inputmethod.CorrectionInfo"); + private static final Class<?>[] INPUT_TYPE_CorrectionInfo = new Class<?>[] { int.class, + CharSequence.class, CharSequence.class }; + private static final Constructor<?> CONSTRUCTOR_CorrectionInfo = CompatUtils + .getConstructor(CLASS_CorrectionInfo, INPUT_TYPE_CorrectionInfo); + private static final Method METHOD_InputConnection_commitCorrection = CompatUtils + .getMethod(InputConnection.class, "commitCorrection", CLASS_CorrectionInfo); + + public static void commitCorrection(InputConnection ic, int offset, CharSequence oldText, + CharSequence newText) { + if (ic == null || CONSTRUCTOR_CorrectionInfo == null + || METHOD_InputConnection_commitCorrection == null) { + return; + } + Object[] args = { offset, oldText, newText }; + try { + Object correctionInfo = CONSTRUCTOR_CorrectionInfo.newInstance(args); + CompatUtils.invoke(ic, null, METHOD_InputConnection_commitCorrection, + correctionInfo); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error in commitCorrection: IllegalArgumentException"); + } catch (InstantiationException e) { + Log.e(TAG, "Error in commitCorrection: InstantiationException"); + } catch (IllegalAccessException e) { + Log.e(TAG, "Error in commitCorrection: IllegalAccessException"); + } catch (InvocationTargetException e) { + Log.e(TAG, "Error in commitCorrection: InvocationTargetException"); + } + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java new file mode 100644 index 000000000..8e22bbc79 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.content.pm.ServiceInfo; +import android.view.inputmethod.InputMethodInfo; + +import java.lang.reflect.Method; + +public class InputMethodInfoCompatWrapper { + private final InputMethodInfo mImi; + private static final Method METHOD_getSubtypeAt = CompatUtils.getMethod( + InputMethodInfo.class, "getSubtypeAt", int.class); + private static final Method METHOD_getSubtypeCount = CompatUtils.getMethod( + InputMethodInfo.class, "getSubtypeCount"); + + public InputMethodInfoCompatWrapper(InputMethodInfo imi) { + mImi = imi; + } + + public InputMethodInfo getInputMethodInfo() { + return mImi; + } + + public String getId() { + return mImi.getId(); + } + + public String getPackageName() { + return mImi.getPackageName(); + } + + public ServiceInfo getServiceInfo() { + return mImi.getServiceInfo(); + } + + public int getSubtypeCount() { + return (Integer) CompatUtils.invoke(mImi, 0, METHOD_getSubtypeCount); + } + + public InputMethodSubtypeCompatWrapper getSubtypeAt(int index) { + return new InputMethodSubtypeCompatWrapper(CompatUtils.invoke(mImi, null, + METHOD_getSubtypeAt, index)); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java new file mode 100644 index 000000000..5e66bf4d9 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.content.Context; +import android.os.IBinder; +import android.util.Log; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// TODO: Override this class with the concrete implementation if we need to take care of the +// performance. +public class InputMethodManagerCompatWrapper { + private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName(); + private static final Method METHOD_getCurrentInputMethodSubtype = + CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype"); + private static final Method METHOD_getEnabledInputMethodSubtypeList = + CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList", + InputMethodInfo.class, boolean.class); + private static final Method METHOD_getShortcutInputMethodsAndSubtypes = + CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes"); + private static final Method METHOD_setInputMethodAndSubtype = + CompatUtils.getMethod( + InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class, + String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype); + private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod( + InputMethodManager.class, "switchToLastInputMethod", IBinder.class); + + private static final InputMethodManagerCompatWrapper sInstance = + new InputMethodManagerCompatWrapper(); + + private InputMethodManager mImm; + private InputMethodManagerCompatWrapper() { + } + + public static InputMethodManagerCompatWrapper getInstance(Context context) { + if (sInstance.mImm == null) { + sInstance.init(context); + } + return sInstance; + } + + private synchronized void init(Context context) { + mImm = (InputMethodManager) context.getSystemService( + Context.INPUT_METHOD_SERVICE); + } + + public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() { + return new InputMethodSubtypeCompatWrapper( + CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype)); + } + + 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(); + return CompatUtils.copyInputMethodSubtypeListToWrapper((List<?>)retval); + } + + 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(); + Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcutMap = + new HashMap<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>>(); + final Map<?, ?> retvalMap = (Map<?, ?>)retval; + for (Object key : retvalMap.keySet()) { + if (!(key instanceof InputMethodInfo)) { + Log.e(TAG, "Class type error."); + return null; + } + shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key), + CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key))); + } + return shortcutMap; + } + + public void setInputMethodAndSubtype( + IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) { + CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype, + token, id, subtype.getOriginalObject()); + } + + public boolean switchToLastInputMethod(IBinder token) { + return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token); + } + + public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() { + if (mImm == null) return null; + List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>(); + for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) { + imis.add(new InputMethodInfoCompatWrapper(imi)); + } + return imis; + } + + public void showInputMethodPicker() { + if (mImm == null) return; + mImm.showInputMethodPicker(); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java new file mode 100644 index 000000000..e02aac704 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 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.compat; + +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.inputmethodservice.InputMethodService; +import android.view.View; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.HorizontalScrollView; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class InputMethodServiceCompatWrapper extends InputMethodService { + private static final Method METHOD_HorizontalScrollView_setOverScrollMode = + CompatUtils.getMethod(HorizontalScrollView.class, "setOverScrollMode", int.class); + private static final Field FIELD_View_OVER_SCROLL_NEVER = + CompatUtils.getField(View.class, "OVER_SCROLL_NEVER"); + private static final Integer View_OVER_SCROLL_NEVER = + (Integer)CompatUtils.getFieldValue(null, null, FIELD_View_OVER_SCROLL_NEVER); + // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10 + // or previous. Note that InputMethodSubtype was added in the API level 11. + // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged(). + // For the API level 10 or previous, we handle the "subtype changed" events by ourselves + // without having support from framework -- onCurrentInputMethodSubtypeChanged(). + private static final boolean CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED = true; + + private InputMethodManagerCompatWrapper mImm; + + @Override + public void onCreate() { + super.onCreate(); + mImm = InputMethodManagerCompatWrapper.getInstance(this); + } + + // When the API level is 10 or previous, notifyOnCurrentInputMethodSubtypeChanged should + // handle the event the current subtype was changed. LatinIME calls + // notifyOnCurrentInputMethodSubtypeChanged every time LatinIME + // changes the current subtype. + // This call is required to let LatinIME itself know a subtype changed + // event when the API level is 10 or previous. + @SuppressWarnings("unused") + public void notifyOnCurrentInputMethodSubtypeChanged(InputMethodSubtypeCompatWrapper subtype) { + // Do nothing when the API level is 11 or later + if (CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return; + if (subtype == null) { + subtype = mImm.getCurrentInputMethodSubtype(); + } + if (subtype != null) { + SubtypeSwitcher.getInstance().updateSubtype(subtype); + } + } + + protected static void setOverScrollModeNever(HorizontalScrollView scrollView) { + if (View_OVER_SCROLL_NEVER != null) { + CompatUtils.invoke(scrollView, null, METHOD_HorizontalScrollView_setOverScrollMode, + View_OVER_SCROLL_NEVER); + } + } + + ////////////////////////////////////// + // Functions using API v11 or later // + ////////////////////////////////////// + @Override + public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { + // Do nothing when the API level is 10 or previous + if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return; + SubtypeSwitcher.getInstance().updateSubtype( + new InputMethodSubtypeCompatWrapper(subtype)); + } + + protected static void setTouchableRegionCompat(InputMethodService.Insets outInsets, + int x, int y, int width, int height) { + outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; + outInsets.touchableRegion.set(x, y, width, height); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java new file mode 100644 index 000000000..3ffa81932 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2011 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.compat; + +import com.android.inputmethod.latin.LatinImeLogger; + +import android.text.TextUtils; +import android.util.Log; + +import java.lang.reflect.Method; + +// TODO: Override this class with the concrete implementation if we need to take care of the +// performance. +public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper { + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = InputMethodSubtypeCompatWrapper.class.getSimpleName(); + private static final String DEFAULT_LOCALE = "en_US"; + private static final String DEFAULT_MODE = "keyboard"; + + public static final Class<?> CLASS_InputMethodSubtype = + CompatUtils.getClass("android.view.inputmethod.InputMethodSubtype"); + private static final Method METHOD_getNameResId = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getNameResId"); + private static final Method METHOD_getIconResId = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getIconResId"); + private static final Method METHOD_getLocale = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getLocale"); + private static final Method METHOD_getMode = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getMode"); + private static final Method METHOD_getExtraValue = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValue"); + private static final Method METHOD_containsExtraValueKey = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "containsExtraValueKey", String.class); + private static final Method METHOD_getExtraValueOf = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class); + + public InputMethodSubtypeCompatWrapper(Object subtype) { + super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype)) + ? subtype : null); + if (DBG) { + Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper"); + } + } + + public int getNameResId() { + return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId); + } + + public int getIconResId() { + return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId); + } + + public String getLocale() { + final String s = (String)CompatUtils.invoke(mObj, null, METHOD_getLocale); + if (TextUtils.isEmpty(s)) return DEFAULT_LOCALE; + return s; + } + + public String getMode() { + String s = (String)CompatUtils.invoke(mObj, null, METHOD_getMode); + if (TextUtils.isEmpty(s)) return DEFAULT_MODE; + return s; + } + + public String getExtraValue() { + return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue); + } + + public boolean containsExtraValueKey(String key) { + return (Boolean)CompatUtils.invoke(mObj, false, METHOD_containsExtraValueKey, key); + } + + public String getExtraValueOf(String key) { + return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValueOf, key); + } + + @Override + public boolean equals(Object o) { + if (o instanceof InputMethodSubtypeCompatWrapper) { + InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o; + return mObj.equals(subtype.getOriginalObject()); + } else { + return mObj.equals(o); + } + } + +} diff --git a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java new file mode 100644 index 000000000..d85174188 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.text.InputType; + +import java.lang.reflect.Field; + +public class InputTypeCompatUtils { + private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = + CompatUtils.getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); + private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = CompatUtils + .getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_PASSWORD"); + private static final Field FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD = CompatUtils + .getField(InputType.class, "TYPE_NUMBER_VARIATION_PASSWORD"); + private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS); + private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD); + private static final Integer OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD); + private static final int WEB_TEXT_PASSWORD_INPUT_TYPE; + private static final int NUMBER_PASSWORD_INPUT_TYPE; + private static final int TEXT_PASSWORD_INPUT_TYPE = + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + + static { + WEB_TEXT_PASSWORD_INPUT_TYPE = + OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD != null + ? InputType.TYPE_CLASS_TEXT | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD + : 0; + NUMBER_PASSWORD_INPUT_TYPE = + OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD != null + ? InputType.TYPE_CLASS_NUMBER | OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD + : 0; + } + + private static boolean isWebPasswordInputType(int inputType) { + return WEB_TEXT_PASSWORD_INPUT_TYPE != 0 + && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isNumberPasswordInputType(int inputType) { + return NUMBER_PASSWORD_INPUT_TYPE != 0 + && inputType == NUMBER_PASSWORD_INPUT_TYPE; + } + + private static boolean isTextPasswordInputType(int inputType) { + return inputType == TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isWebEmailAddressVariation(int variation) { + return OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null + && variation == OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + + public static boolean isEmailVariation(int variation) { + return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || isWebEmailAddressVariation(variation); + } + + // Please refer to TextView.isPasswordInputType + public static boolean isPasswordInputType(int inputType) { + final int maskedInputType = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) + || isNumberPasswordInputType(maskedInputType); + } + + // Please refer to TextView.isVisiblePasswordInputType + public static boolean isVisiblePasswordInputType(int inputType) { + final int maskedInputType = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE; + } +} diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java new file mode 100644 index 000000000..8e2a2e0b8 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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.compat; + +import android.content.Context; +import android.os.Vibrator; + +import java.lang.reflect.Method; + +public class VibratorCompatWrapper { + private static final Method METHOD_hasVibrator = CompatUtils.getMethod(Vibrator.class, + "hasVibrator", int.class); + + private static final VibratorCompatWrapper sInstance = new VibratorCompatWrapper(); + private Vibrator mVibrator; + + private VibratorCompatWrapper() { + } + + public static VibratorCompatWrapper getInstance(Context context) { + if (sInstance.mVibrator == null) { + sInstance.mVibrator = + (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + return sInstance; + } + + public boolean hasVibrator() { + if (mVibrator == null) + return false; + return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator); + } +} diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java index 105656fe0..5fba29d90 100644 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java @@ -14,8 +14,14 @@ * the License. */ -package com.android.inputmethod.voice; - +package com.android.inputmethod.deprecated; + +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.deprecated.voice.FieldContext; +import com.android.inputmethod.deprecated.voice.Hints; +import com.android.inputmethod.deprecated.voice.SettingsUtil; +import com.android.inputmethod.deprecated.voice.VoiceInput; +import com.android.inputmethod.deprecated.voice.VoiceInputLogger; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.EditingUtils; import com.android.inputmethod.latin.LatinIME; @@ -28,6 +34,7 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.Utils; import android.app.AlertDialog; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -54,7 +61,6 @@ import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import java.util.ArrayList; @@ -62,8 +68,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class VoiceIMEConnector implements VoiceInput.UiListener { - private static final VoiceIMEConnector sInstance = new VoiceIMEConnector(); +public class VoiceProxy implements VoiceInput.UiListener { + private static final VoiceProxy sInstance = new VoiceProxy(); public static final boolean VOICE_INSTALLED = true; private static final boolean ENABLE_VOICE_BUTTON = true; @@ -77,7 +83,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { "has_used_voice_input_unsupported_locale"; private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; - private static final String TAG = VoiceIMEConnector.class.getSimpleName(); + private static final String TAG = VoiceProxy.class.getSimpleName(); private static final boolean DEBUG = LatinImeLogger.sDBG; private boolean mAfterVoiceInput; @@ -93,7 +99,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { private boolean mVoiceButtonOnPrimary; private boolean mVoiceInputHighlighted; - private InputMethodManager mImm; + private InputMethodManagerCompatWrapper mImm; private LatinIME mService; private AlertDialog mVoiceWarningDialog; private VoiceInput mVoiceInput; @@ -105,19 +111,19 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { private final Map<String, List<CharSequence>> mWordToSuggestions = new HashMap<String, List<CharSequence>>(); - public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) { + public static VoiceProxy init(LatinIME context, SharedPreferences prefs, UIHandler h) { sInstance.initInternal(context, prefs, h); return sInstance; } - public static VoiceIMEConnector getInstance() { + public static VoiceProxy getInstance() { return sInstance; } private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { mService = service; mHandler = h; - mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mImm = InputMethodManagerCompatWrapper.getInstance(service); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); if (VOICE_INSTALLED) { mVoiceInput = new VoiceInput(service, this); @@ -133,7 +139,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { } } - private VoiceIMEConnector() { + private VoiceProxy() { // Intentional empty constructor for singleton. } @@ -560,14 +566,24 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { @Override protected void onPostExecute(Boolean result) { + // Calls in this method need to be done in the same thread as the thread which + // called switchToLastInputMethod() if (!result) { if (DEBUG) { Log.d(TAG, "Couldn't switch back to last IME."); } - // Needs to reset here because LatinIME failed to back to any IME and - // the same voice subtype will be triggered in the next time. + // Because the current IME and subtype failed to switch to any other IME and + // subtype by switchToLastInputMethod, the current IME and subtype should keep + // being LatinIME and voice subtype in the next time. And for re-showing voice + // mode, the state of voice input should be reset and the voice view should be + // hidden. mVoiceInput.reset(); mService.requestHideSelf(0); + } else { + // Notify an event that the current subtype was changed. This event will be + // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented + // when the API level is 10 or previous. + mService.notifyOnCurrentInputMethodSubtypeChanged(null); } } }.execute(); @@ -624,6 +640,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { } private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { + @SuppressWarnings("deprecation") final boolean noMic = Utils.inPrivateImeOptions(null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) || Utils.inPrivateImeOptions(mService.getPackageName(), @@ -674,7 +691,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { public void onAttachedToWindow() { // After onAttachedToWindow, we can show the voice warning dialog. See startListening() // above. - mSubtypeSwitcher.setVoiceInput(mVoiceInput); + VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher); } public void onConfigurationChanged(Configuration configuration) { @@ -725,4 +742,91 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { List<String> candidates; Map<String, List<CharSequence>> alternatives; } + + public static class VoiceLoggerWrapper { + private static final VoiceLoggerWrapper sInstance = new VoiceLoggerWrapper(); + private VoiceInputLogger mLogger; + + public static VoiceLoggerWrapper getInstance(Context context) { + if (sInstance.mLogger == null) { + // Not thread safe, but it's ok. + sInstance.mLogger = VoiceInputLogger.getLogger(context); + } + return sInstance; + } + + // private for the singleton + private VoiceLoggerWrapper() { + } + + public void settingsWarningDialogCancel() { + mLogger.settingsWarningDialogCancel(); + } + + public void settingsWarningDialogOk() { + mLogger.settingsWarningDialogOk(); + } + + public void settingsWarningDialogShown() { + mLogger.settingsWarningDialogShown(); + } + + public void settingsWarningDialogDismissed() { + mLogger.settingsWarningDialogDismissed(); + } + + public void voiceInputSettingEnabled(boolean enabled) { + if (enabled) { + mLogger.voiceInputSettingEnabled(); + } else { + mLogger.voiceInputSettingDisabled(); + } + } + } + + public static class VoiceInputWrapper { + private static final VoiceInputWrapper sInstance = new VoiceInputWrapper(); + private VoiceInput mVoiceInput; + public static VoiceInputWrapper getInstance() { + return sInstance; + } + public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) { + if (mVoiceInput == null && voiceInput != null) { + mVoiceInput = voiceInput; + } + switcher.setVoiceInputWrapper(this); + } + + private VoiceInputWrapper() { + } + + public void cancel() { + if (mVoiceInput != null) mVoiceInput.cancel(); + } + + public void reset() { + if (mVoiceInput != null) mVoiceInput.reset(); + } + } + + // A list of locales which are supported by default for voice input, unless we get a + // different list from Gservices. + private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = + "en " + + "en_US " + + "en_GB " + + "en_AU " + + "en_CA " + + "en_IE " + + "en_IN " + + "en_NZ " + + "en_SG " + + "en_ZA "; + + public static String getSupportedLocalesString (ContentResolver resolver) { + return SettingsUtil.getSettingsString( + resolver, + SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, + DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + } } diff --git a/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java new file mode 100644 index 000000000..488390fbc --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/compat/VoiceInputLoggerCompatUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 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.deprecated.compat; + +import com.android.common.userhappiness.UserHappinessSignals; +import com.android.inputmethod.compat.CompatUtils; + +import java.lang.reflect.Method; + +public class VoiceInputLoggerCompatUtils { + public static final String EXTRA_TEXT_REPLACED_LENGTH = "length"; + public static final String EXTRA_BEFORE_N_BEST_CHOOSE = "before"; + public static final String EXTRA_AFTER_N_BEST_CHOOSE = "after"; + private static final Method METHOD_UserHappinessSignals_setHasVoiceLoggingInfo = + CompatUtils.getMethod(UserHappinessSignals.class, "setHasVoiceLoggingInfo", + boolean.class); + + public static void setHasVoiceLoggingInfoCompat(boolean hasLoggingInfo) { + CompatUtils.invoke(null, null, METHOD_UserHappinessSignals_setHasVoiceLoggingInfo, + hasLoggingInfo); + } +} diff --git a/java/src/com/android/inputmethod/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java index dfdfbaa9f..0ef73d2d7 100644 --- a/java/src/com/android/inputmethod/voice/FieldContext.java +++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import android.os.Bundle; import android.util.Log; diff --git a/java/src/com/android/inputmethod/voice/Hints.java b/java/src/com/android/inputmethod/deprecated/voice/Hints.java index d11d3b042..52a4f4e58 100644 --- a/java/src/com/android/inputmethod/voice/Hints.java +++ b/java/src/com/android/inputmethod/deprecated/voice/Hints.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SharedPreferencesCompat; diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java index 95a79f463..52c73ce90 100644 --- a/java/src/com/android/inputmethod/voice/RecognitionView.java +++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; diff --git a/java/src/com/android/inputmethod/voice/SettingsUtil.java b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java index 4d746e120..7721fe268 100644 --- a/java/src/com/android/inputmethod/voice/SettingsUtil.java +++ b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import android.content.ContentResolver; import android.provider.Settings; diff --git a/java/src/com/android/inputmethod/voice/SoundIndicator.java b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java index 543290b32..8cc79de1e 100644 --- a/java/src/com/android/inputmethod/voice/SoundIndicator.java +++ b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import android.content.Context; import android.graphics.Bitmap; diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java index 2df9e8588..7ee0de9c9 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInput.java +++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import com.android.inputmethod.latin.EditingUtils; import com.android.inputmethod.latin.LatinImeLogger; diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java index 3e65434a2..dcd124f70 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java +++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java @@ -14,10 +14,11 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import com.android.common.speech.LoggingEvents; import com.android.common.userhappiness.UserHappinessSignals; +import com.android.inputmethod.deprecated.compat.VoiceInputLoggerCompatUtils; import android.content.Context; import android.content.Intent; @@ -212,13 +213,12 @@ public class VoiceInputLogger { setHasLoggingInfo(true); Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength); + i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength); i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after); + i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_BEFORE_N_BEST_CHOOSE, before); + i.putExtra(VoiceInputLoggerCompatUtils.EXTRA_AFTER_N_BEST_CHOOSE, after); mContext.sendBroadcast(i); } @@ -257,7 +257,7 @@ public class VoiceInputLogger { // 2. type subject in subject field // 3. speak message in message field // 4. press send - UserHappinessSignals.setHasVoiceLoggingInfo(hasLoggingInfo); + VoiceInputLoggerCompatUtils.setHasVoiceLoggingInfoCompat(hasLoggingInfo); } private boolean hasLoggingInfo(){ diff --git a/java/src/com/android/inputmethod/voice/WaveformImage.java b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java index 8bac669fc..a3025f252 100644 --- a/java/src/com/android/inputmethod/voice/WaveformImage.java +++ b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import android.graphics.Bitmap; import android.graphics.Canvas; diff --git a/java/src/com/android/inputmethod/voice/Whitelist.java b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java index f4c24de0c..310689cb2 100644 --- a/java/src/com/android/inputmethod/voice/Whitelist.java +++ b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.voice; +package com.android.inputmethod.deprecated.voice; import android.os.Bundle; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index d09f6786e..f68b68f1d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -16,8 +16,9 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.Utils; import android.view.inputmethod.EditorInfo; @@ -62,8 +63,8 @@ public class KeyboardId { this.mMode = mode; this.mXmlId = xmlId; this.mColorScheme = colorScheme; - this.mPasswordInput = Utils.isPasswordInputType(inputType) - || Utils.isVisiblePasswordInputType(inputType); + this.mPasswordInput = InputTypeCompatUtils.isPasswordInputType(inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(inputType); this.mHasSettingsKey = hasSettingsKey; this.mVoiceKeyEnabled = voiceKeyEnabled; this.mHasVoiceKey = hasVoiceKey; @@ -140,7 +141,7 @@ public class KeyboardId { mLocale, (mOrientation == 1 ? "port" : "land"), modeName(mMode), - imeOptionsName(mImeAction), + EditorInfoCompatUtils.imeOptionsName(mImeAction), (mPasswordInput ? " passwordInput" : ""), (mHasSettingsKey ? " hasSettingsKey" : ""), (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), @@ -170,26 +171,4 @@ public class KeyboardId { } return null; } - - public static String imeOptionsName(int imeOptions) { - if (imeOptions == -1) return null; - final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION; - final String action; - switch (actionNo) { - case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break; - case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break; - case EditorInfo.IME_ACTION_GO: action = "actionGo"; break; - case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break; - case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break; - case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break; - case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break; - default: action = "actionUnknown(" + actionNo + ")"; break; - } - if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { - return "flagNoEnterAction|" + action; - } else { - return action; - } - } } - diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index feb56ab3a..62e6f302d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.latin.R; import org.xmlpull.v1.XmlPullParser; @@ -453,7 +454,7 @@ public class KeyboardParser { booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), - textAttr(KeyboardId.imeOptionsName( + textAttr(EditorInfoCompatUtils.imeOptionsName( a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"), textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 64a23ab92..cfa3c446e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -29,7 +30,6 @@ import android.content.res.Resources; import android.util.Log; import android.view.InflateException; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import java.lang.ref.SoftReference; import java.util.HashMap; @@ -752,8 +752,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW)) || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO)) && Utils.hasMultipleEnabledIMEsOrSubtypes( - ((InputMethodManager) context.getSystemService( - Context.INPUT_METHOD_SERVICE))))) { + (InputMethodManagerCompatWrapper.getInstance(context))))) { return true; } return false; diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 77e9caecc..d6c3723fd 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -16,9 +16,9 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.Utils; -import com.android.inputmethod.voice.VoiceIMEConnector; import android.content.Context; import android.graphics.Canvas; @@ -264,6 +264,6 @@ public class LatinKeyboardView extends KeyboardView { @Override protected void onAttachedToWindow() { // Token is available from here. - VoiceIMEConnector.getInstance().onAttachedToWindow(); + VoiceProxy.getInstance().onAttachedToWindow(); } } diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java new file mode 100644 index 000000000..074ecacc5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 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 java.io.File; + +/** + * Immutable class to hold the address of an asset. + * As opposed to a normal file, an asset is usually represented as a contiguous byte array in + * the package file. Open it correctly thus requires the name of the package it is in, but + * also the offset in the file and the length of this data. This class encapsulates these three. + */ +class AssetFileAddress { + public final String mFilename; + public final long mOffset; + public final long mLength; + + public AssetFileAddress(final String filename, final long offset, final long length) { + mFilename = filename; + mOffset = offset; + mLength = length; + } + + public static AssetFileAddress makeFromFileName(final String filename) { + if (null == filename) return null; + File f = new File(filename); + if (null == f || !f.isFile()) return null; + return new AssetFileAddress(filename, 0l, f.length()); + } + + public static AssetFileAddress makeFromFileNameAndOffset(final String filename, + final long offset, final long length) { + if (null == filename) return null; + File f = new File(filename); + if (null == f || !f.isFile()) return null; + return new AssetFileAddress(filename, offset, length); + } +} diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index 092f7ad63..d3119792c 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -48,7 +48,7 @@ public class AutoCorrection { } public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries, - WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities, + WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores, CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, CharSequence quickFixedWord, CharSequence whitelistedWord) { if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) { @@ -62,7 +62,7 @@ public class AutoCorrection { mHasAutoCorrection = true; mAutoCorrectionWord = quickFixedWord; } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode, - priorities, typedWord, autoCorrectionThreshold)) { + sortedScores, typedWord, autoCorrectionThreshold)) { mHasAutoCorrection = true; mAutoCorrectionWord = suggestions.get(0); } @@ -114,13 +114,13 @@ public class AutoCorrection { } private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, - ArrayList<CharSequence> suggestions, int correctionMode, int[] priorities, + ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores, CharSequence typedWord, double autoCorrectionThreshold) { if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) - && typedWord != null && suggestions.size() > 0 && priorities.length > 0) { + && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) { final CharSequence autoCorrectionCandidate = suggestions.get(0); - final int autoCorrectionCandidateScore = priorities[0]; + final int autoCorrectionCandidateScore = sortedScores[0]; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. mNormalizedScore = Utils.calcNormalizedScore( diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 08ddd25fa..fa90fce67 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -26,14 +26,18 @@ import android.util.Log; import java.io.File; import java.util.Arrays; +import java.util.Locale; /** * Implements a static, compacted, binary dictionary of standard words. */ public class BinaryDictionary extends Dictionary { + public static final String DICTIONARY_PACK_AUTHORITY = + "com.android.inputmethod.latin.dictionarypack"; + /** - * There is difference between what java and native code can handle. + * There is a difference between what java and native code can handle. * This value should only be used in BinaryDictionary.java * It is necessary to keep it at this value because some languages e.g. German have * really long words. @@ -54,38 +58,42 @@ public class BinaryDictionary extends Dictionary { private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; - private final int[] mFrequencies = new int[MAX_WORDS]; - private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS]; + private final int[] mScores = new int[MAX_WORDS]; + private final int[] mBigramScores = new int[MAX_BIGRAMS]; private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - - private static class Flags { - private static class FlagEntry { - public final String mName; - public final int mValue; - public FlagEntry(String name, int value) { - mName = name; - mValue = value; - } + + public static class Flag { + public final String mName; + public final int mValue; + + public Flag(String name, int value) { + mName = name; + mValue = value; } - public static final FlagEntry[] ALL_FLAGS = { - // Here should reside all flags that trigger some special processing - // These *must* match the definition in UnigramDictionary enum in - // unigram_dictionary.h so please update both at the same time. - new FlagEntry("requiresGermanUmlautProcessing", 0x1) - }; } + + public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING = + new Flag("requiresGermanUmlautProcessing", 0x1); + + private static final Flag[] ALL_FLAGS = { + // Here should reside all flags that trigger some special processing + // These *must* match the definition in UnigramDictionary enum in + // unigram_dictionary.h so please update both at the same time. + FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING, + }; + private int mFlags = 0; private BinaryDictionary() { } /** - * Initialize a dictionary from a raw resource file + * Initializes a dictionary from a raw resource file * @param context application context for reading resources * @param resId the resource containing the raw binary dictionary - * @return initialized instance of BinaryDictionary + * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* + * @return an initialized instance of BinaryDictionary */ public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { synchronized (sInstance) { @@ -110,12 +118,12 @@ public class BinaryDictionary extends Dictionary { return null; } } - sInstance.initFlags(); + sInstance.mFlags = initFlags(ALL_FLAGS, SubtypeSwitcher.getInstance()); return sInstance; } /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset, - long length, int dicTypeId) { + long length, int dicTypeId, Flag[] flagArray) { synchronized (sInstance) { sInstance.closeInternal(); if (dictionary.isFile()) { @@ -126,22 +134,54 @@ public class BinaryDictionary extends Dictionary { return null; } } + sInstance.mFlags = initFlags(flagArray, null); return sInstance; } - private void initFlags() { + private static int initFlags(Flag[] flagArray, SubtypeSwitcher switcher) { int flags = 0; - for (Flags.FlagEntry entry : Flags.ALL_FLAGS) { - if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName)) + for (Flag entry : flagArray) { + if (switcher == null || switcher.currentSubtypeContainsExtraValueKey(entry.mName)) flags |= entry.mValue; } - mFlags = flags; + return flags; } static { Utils.loadNativeLibrary(); } + /** + * Initializes a dictionary from a dictionary pack. + * + * This searches for a content provider providing a dictionary pack for the specified + * locale. If none is found, it falls back to using the resource passed as fallBackResId + * as a dictionary. + * @param context application context for reading resources + * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* + * @param locale the locale for which to create the dictionary + * @param fallBackResId the id of the resource to use as a fallback if no pack is found + * @return an initialized instance of BinaryDictionary + */ + public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId, + Locale locale, int fallbackResId) { + if (null == locale) { + Log.e(TAG, "No locale defined for dictionary"); + return initDictionary(context, fallbackResId, dicTypeId); + } + synchronized (sInstance) { + sInstance.closeInternal(); + + final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale, + context, fallbackResId); + if (null != dictFile) { + sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength); + sInstance.mDicTypeId = dicTypeId; + } + } + return sInstance; + } + private native int openNative(String sourceDir, long dictOffset, long dictSize, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); @@ -149,14 +189,14 @@ public class BinaryDictionary extends Dictionary { private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars, - int[] frequencies); + int[] scores); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, - int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, + int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, int maxWordLength, int maxBigrams, int maxAlternatives); private final void loadDictionary(String path, long startOffset, long length) { mNativeDict = openNative(path, startOffset, length, - TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, + TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); mDictLength = length; } @@ -168,7 +208,7 @@ public class BinaryDictionary extends Dictionary { char[] chars = previousWord.toString().toCharArray(); Arrays.fill(mOutputChars_bigrams, (char) 0); - Arrays.fill(mFrequencies_bigrams, 0); + Arrays.fill(mBigramScores, 0); int codesSize = codes.size(); Arrays.fill(mInputCodes, -1); @@ -177,18 +217,18 @@ public class BinaryDictionary extends Dictionary { Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, - mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS, + mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS, MAX_PROXIMITY_CHARS_SIZE); for (int j = 0; j < count; ++j) { - if (mFrequencies_bigrams[j] < 1) break; + if (mBigramScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) { ++len; } if (len > 0) { - callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j], + callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], mDicTypeId, DataType.BIGRAM); } } @@ -197,17 +237,17 @@ public class BinaryDictionary extends Dictionary { @Override public void getWords(final WordComposer codes, final WordCallback callback) { final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(), - mOutputChars, mFrequencies); + mOutputChars, mScores); for (int j = 0; j < count; ++j) { - if (mFrequencies[j] < 1) break; + if (mScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { ++len; } if (len > 0) { - callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId, + callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, DataType.UNIGRAM); } } @@ -218,7 +258,7 @@ public class BinaryDictionary extends Dictionary { } /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard, - char[] outputChars, int[] frequencies) { + char[] outputChars, int[] scores) { if (!isValidDictionary()) return -1; final int codesSize = codes.size(); @@ -232,12 +272,12 @@ public class BinaryDictionary extends Dictionary { Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); } Arrays.fill(outputChars, (char) 0); - Arrays.fill(frequencies, 0); + Arrays.fill(scores, 0); return getSuggestionsNative( mNativeDict, keyboard.getProximityInfo(), codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, - mFlags, outputChars, frequencies); + mFlags, outputChars, scores); } @Override diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java new file mode 100644 index 000000000..d0464dd94 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 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 android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.text.TextUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +/** + * Group class for static methods to help with creation and getting of the binary dictionary + * file from the dictionary provider + */ +public class BinaryDictionaryFileDumper { + /** + * The size of the temporary buffer to copy files. + */ + static final int FILE_READ_BUFFER_SIZE = 1024; + + // Prevents this class to be accidentally instantiated. + private BinaryDictionaryFileDumper() { + } + + /** + * Generates a file name that matches the locale passed as an argument. + * The file name is basically the result of the .toString() method, except we replace + * any @File.separator with an underscore to avoid generating a file name that may not + * be created. + * @param locale the locale for which to get the file name + * @param context the context to use for getting the directory + * @return the name of the file to be created + */ + private static String getCacheFileNameForLocale(Locale locale, Context context) { + // The following assumes two things : + // 1. That File.separator is not the same character as "_" + // I don't think any android system will ever use "_" as a path separator + // 2. That no two locales differ by only a File.separator versus a "_" + // Since "_" can't be part of locale components this should be safe. + // Examples: + // en -> en + // en_US_POSIX -> en_US_POSIX + // en__foo/bar -> en__foo_bar + final String[] separator = { File.separator }; + final String[] empty = { "_" }; + final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty); + return context.getFilesDir() + File.separator + basename; + } + + /** + * Return for a given locale the provider URI to query to get the dictionary. + */ + public static Uri getProviderUri(Locale locale) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( + locale.toString()).build(); + } + + /** + * Queries a content provider for dictionary data for some locale and returns it as a file name. + * + * This will query a content provider for dictionary data for a given locale, and return + * the name of a file suitable to be mmap'ed. It will copy it to local storage if needed. + * It should also check the dictionary version to avoid unnecessary copies but this is + * still in TODO state. + * This will make the data from the content provider the cached dictionary for this locale, + * overwriting any previous cached data. + * @returns the name of the file, or null if no data could be obtained. + * @throw FileNotFoundException if the provider returns non-existent data. + * @throw IOException if the provider-returned data could not be read. + */ + public static String getDictionaryFileFromContentProvider(Locale locale, Context context) + throws FileNotFoundException, IOException { + // TODO: check whether the dictionary is the same or not and if it is, return the cached + // file. + final ContentResolver resolver = context.getContentResolver(); + final Uri dictionaryPackUri = getProviderUri(locale); + final InputStream stream = resolver.openInputStream(dictionaryPackUri); + if (null == stream) return null; + return copyFileTo(stream, getCacheFileNameForLocale(locale, context)); + } + + /** + * Accepts a file as dictionary data for some locale and returns the name of a file. + * + * This will make the data in the input file the cached dictionary for this locale, overwriting + * any previous cached data. + */ + public static String getDictionaryFileFromFile(String fileName, Locale locale, + Context context) throws FileNotFoundException, IOException { + return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale, + context)); + } + + /** + * Accepts a resource number as dictionary data for some locale and returns the name of a file. + * + * This will make the resource the cached dictionary for this locale, overwriting any previous + * cached data. + */ + public static String getDictionaryFileFromResource(int resource, Locale locale, + Context context) throws FileNotFoundException, IOException { + return copyFileTo(context.getResources().openRawResource(resource), + getCacheFileNameForLocale(locale, context)); + } + + /** + * Copies the data in an input stream to a target file, creating the file if necessary and + * overwriting it if it already exists. + */ + private static String copyFileTo(final InputStream input, final String outputFileName) + throws FileNotFoundException, IOException { + final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; + final FileOutputStream output = new FileOutputStream(outputFileName); + for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) + output.write(buffer, 0, readBytes); + input.close(); + return outputFileName; + } +} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java new file mode 100644 index 000000000..72512c7e1 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 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 android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.util.Log; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Locale; + +/** + * Helper class to get the address of a mmap'able dictionary file. + */ +class BinaryDictionaryGetter { + + /** + * Used for Log actions from this class + */ + private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); + + // Prevents this from being instantiated + private BinaryDictionaryGetter() {} + + /** + * Returns a file address from a resource, or null if it cannot be opened. + */ + private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) { + final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); + if (afd == null) { + Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" + + fallbackResId); + return null; + } + return AssetFileAddress.makeFromFileNameAndOffset( + context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); + } + + /** + * Returns a file address for a given locale, trying relevant methods in order. + * + * Tries to get a binary dictionary from various sources, in order: + * - Uses a private method of getting a private dictionary, as implemented by the + * PrivateBinaryDictionaryGetter class. + * If that fails: + * - Uses a content provider to get a public dictionary, as per the protocol described + * in BinaryDictionaryFileDumper. + * If that fails: + * - Gets a file name from the fallback resource passed as an argument. + * If that fails: + * - Returns null. + * @return The address of a valid file, or null. + * @throws FileNotFoundException if a dictionary provider returned a file name, but the + * file cannot be found. + * @throws IOException if there was an I/O problem reading or copying a file. + */ + public static AssetFileAddress getDictionaryFile(Locale locale, Context context, + int fallbackResId) { + // Try first to query a private file signed the same way. + final AssetFileAddress privateFile = + PrivateBinaryDictionaryGetter.getDictionaryFile(locale, context); + if (null != privateFile) { + return privateFile; + } else { + try { + // If that was no-go, try to find a publicly exported dictionary. + final String fileName = BinaryDictionaryFileDumper. + getDictionaryFileFromContentProvider(locale, context); + return AssetFileAddress.makeFromFileName(fileName); + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to create dictionary file from provider for locale " + + locale.toString() + ": falling back to internal dictionary"); + return loadFallbackResource(context, fallbackResId); + } catch (IOException e) { + Log.e(TAG, "Unable to read source data for locale " + + locale.toString() + ": falling back to internal dictionary"); + return loadFallbackResource(context, fallbackResId); + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 56f0cc503..ac43d6477 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -29,7 +29,7 @@ public abstract class Dictionary { /** * The weight to give to a word if it's length is the same as the number of typed characters. */ - protected static final int FULL_WORD_FREQ_MULTIPLIER = 2; + protected static final int FULL_WORD_SCORE_MULTIPLIER = 2; public static enum DataType { UNIGRAM, BIGRAM @@ -42,17 +42,17 @@ public abstract class Dictionary { public interface WordCallback { /** * Adds a word to a list of suggestions. The word is expected to be ordered based on - * the provided frequency. + * the provided score. * @param word the character array containing the word * @param wordOffset starting offset of the word in the character array * @param wordLength length of valid characters in the character array - * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but + * @param score the score of occurrence. This is normalized between 1 and 255, but * can exceed those limits * @param dicTypeId of the dictionary where word was from * @param dataType tells type of this data * @return true if the word was added, false if no more words are required */ - boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId, + boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId, DataType dataType); } diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java new file mode 100644 index 000000000..c7864621d --- /dev/null +++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 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 android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.net.Uri; + +/** + * Takes action to reload the necessary data when a dictionary pack was added/removed. + */ +public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver { + + final LatinIME mService; + + public DictionaryPackInstallBroadcastReceiver(final LatinIME service) { + mService = service; + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final PackageManager manager = context.getPackageManager(); + + // We need to reread the dictionary if a new dictionary package is installed. + if (action.equals(Intent.ACTION_PACKAGE_ADDED)) { + final Uri packageUri = intent.getData(); + if (null == packageUri) return; // No package name : we can't do anything + final String packageName = packageUri.getSchemeSpecificPart(); + if (null == packageName) return; + final PackageInfo packageInfo; + try { + packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS); + } catch (android.content.pm.PackageManager.NameNotFoundException e) { + return; // No package info : we can't do anything + } + final ProviderInfo[] providers = packageInfo.providers; + if (null == providers) return; // No providers : it is not a dictionary. + + // Search for some dictionary pack in the just-installed package. If found, reread. + for (ProviderInfo info : providers) { + if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) { + mService.resetSuggestMainDict(); + return; + } + } + // If we come here none of the authorities matched the one we searched for. + // We can exit safely. + return; + } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) + && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // When the dictionary package is removed, we need to reread dictionary (to use the + // next-priority one, or stop using a dictionary at all if this was the only one, + // since this is the user request). + // If we are replacing the package, we will receive ADDED right away so no need to + // remove the dictionary at the moment, since we will do it when we receive the + // ADDED broadcast. + + // TODO: Only reload dictionary on REMOVED when the removed package is the one we + // read dictionary from? + mService.resetSuggestMainDict(); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 0318175f6..d87fbce51 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -327,7 +327,7 @@ public class ExpandableDictionary extends Dictionary { final int finalFreq; if (skipPos < 0) { finalFreq = freq * snr * addedAttenuation - * FULL_WORD_FREQ_MULTIPLIER; + * FULL_WORD_SCORE_MULTIPLIER; } else { finalFreq = computeSkippedWordFinalFreq(freq, snr * addedAttenuation, mInputLength); diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java index 5587c685f..be5e015aa 100644 --- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -106,8 +106,8 @@ public class InputLanguageSelection extends PreferenceActivity { conf.locale = locale; res.updateConfiguration(conf, res.getDisplayMetrics()); - int mainDicResId = Utils.getMainDictionaryResourceId(res); - BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN); + BinaryDictionary bd = BinaryDictionary.initDictionaryFromManager(this, Suggest.DIC_MAIN, + locale, Utils.getMainDictionaryResourceId(res)); // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words. diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 6e76cadf2..ae2315437 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,6 +16,14 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputConnectionCompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; +import com.android.inputmethod.compat.VibratorCompatWrapper; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; @@ -23,7 +31,6 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.LatinKeyboard; import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.latin.Utils.RingCharBuffer; -import com.android.inputmethod.voice.VoiceIMEConnector; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -42,7 +49,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.SystemClock; -import android.os.Vibrator; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; import android.text.InputType; @@ -61,13 +67,10 @@ import android.view.ViewParent; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; @@ -81,7 +84,7 @@ import java.util.Locale; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodService implements KeyboardActionListener { +public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; @@ -94,6 +97,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed. */ + @SuppressWarnings("dep-ann") public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm"; /** @@ -119,6 +123,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; + /** + * The name of the scheme used by the Package Manager to warn of a new package installation, + * replacement or removal. + */ + private static final String SCHEME_PACKAGE = "package"; + private int mSuggestionVisibility; private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE = R.string.prefs_suggestion_visibility_show_value; @@ -140,13 +150,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private AlertDialog mOptionsDialog; - private InputMethodManager mImm; + private InputMethodManagerCompatWrapper mImm; private Resources mResources; private SharedPreferences mPrefs; private String mInputMethodId; private KeyboardSwitcher mKeyboardSwitcher; private SubtypeSwitcher mSubtypeSwitcher; - private VoiceIMEConnector mVoiceConnector; + private VoiceProxy mVoiceProxy; private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; @@ -204,59 +214,47 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen /* package */ String mWordSeparators; private String mSentenceSeparators; private String mSuggestPuncs; - // TODO: Move this flag to VoiceIMEConnector + // TODO: Move this flag to VoiceProxy private boolean mConfigurationChanging; + // Object for reacting to adding/removing a dictionary pack. + private BroadcastReceiver mDictionaryPackInstallReceiver = + new DictionaryPackInstallBroadcastReceiver(this); + // Keeps track of most recently inserted text (multi-character key) for reverting private CharSequence mEnteredText; private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); - public abstract static class WordAlternatives { - protected CharSequence mChosenWord; + public class WordAlternatives { + private final CharSequence mChosenWord; + private final WordComposer mWordComposer; - public WordAlternatives() { - // Nothing - } - - public WordAlternatives(CharSequence chosenWord) { + public WordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { mChosenWord = chosenWord; + mWordComposer = wordComposer; } - @Override - public int hashCode() { - return mChosenWord.hashCode(); - } - - public abstract CharSequence getOriginalWord(); - public CharSequence getChosenWord() { return mChosenWord; } - public abstract SuggestedWords.Builder getAlternatives(); - } - - public class TypedWordAlternatives extends WordAlternatives { - private WordComposer word; - - public TypedWordAlternatives() { - // Nothing + public CharSequence getOriginalWord() { + return mWordComposer.getTypedWord(); } - public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { - super(chosenWord); - word = wordComposer; + public SuggestedWords.Builder getAlternatives() { + return getTypedSuggestions(mWordComposer); } @Override - public CharSequence getOriginalWord() { - return word.getTypedWord(); + public int hashCode() { + return mChosenWord.hashCode(); } @Override - public SuggestedWords.Builder getAlternatives() { - return getTypedSuggestions(word); + public boolean equals(Object o) { + return o instanceof CharSequence && TextUtils.equals(mChosenWord, (CharSequence)o); } } @@ -286,7 +284,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen switcher.updateShiftState(); break; case MSG_VOICE_RESULTS: - mVoiceConnector.handleVoiceResults(preferCapitalization() + mVoiceProxy.handleVoiceResults(preferCapitalization() || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked())); break; case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR: @@ -385,7 +383,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onCreate(); - mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); + mImm = InputMethodManagerCompatWrapper.getInstance(this); mInputMethodId = Utils.getInputMethodId(mImm, getPackageName()); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); @@ -430,18 +428,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mOrientation = res.getConfiguration().orientation; initSuggestPuncList(); - // register to receive ringer mode change and network state change. + // Register to receive ringer mode change and network state change. + // Also receive installation and removal of a dictionary pack. final IntentFilter filter = new IntentFilter(); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); - mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); + mVoiceProxy = VoiceProxy.init(this, prefs, mHandler); + + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); } private void initSuggest() { - String locale = mSubtypeSwitcher.getInputLocaleStr(); + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = new Locale(localeStr); - Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale)); + final Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(keyboardLocale); if (mSuggest != null) { mSuggest.close(); } @@ -450,20 +456,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Resources res = mResources; int mainDicResId = Utils.getMainDictionaryResourceId(res); - mSuggest = new Suggest(this, mainDicResId); + mSuggest = new Suggest(this, mainDicResId, keyboardLocale); loadAndSetAutoCorrectionThreshold(prefs); updateAutoTextEnabled(); - mUserDictionary = new UserDictionary(this, locale); + mUserDictionary = new UserDictionary(this, localeStr); mSuggest.setUserDictionary(mUserDictionary); mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); mSuggest.setContactsDictionary(mContactsDictionary); - mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO); + mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO); mSuggest.setAutoDictionary(mAutoDictionary); - mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER); + mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER); mSuggest.setUserBigramDictionary(mUserBigramDictionary); updateCorrectionMode(); @@ -473,6 +479,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.changeSystemLocale(savedLocale); } + /* package private */ void resetSuggestMainDict() { + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = new Locale(localeStr); + int mainDicResId = Utils.getMainDictionaryResourceId(mResources); + mSuggest.resetMainDict(this, mainDicResId, keyboardLocale); + } + @Override public void onDestroy() { if (mSuggest != null) { @@ -480,7 +493,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSuggest = null; } unregisterReceiver(mReceiver); - mVoiceConnector.destroy(); + unregisterReceiver(mDictionaryPackInstallReceiver); + mVoiceProxy.destroy(); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -501,7 +515,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mConfigurationChanging = true; super.onConfigurationChanged(conf); - mVoiceConnector.onConfigurationChanged(conf); + mVoiceProxy.onConfigurationChanged(conf); mConfigurationChanging = false; } @@ -518,7 +532,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (container.getPaddingRight() != 0) { HorizontalScrollView scrollView = (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view); - scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); + setOverScrollModeNever(scrollView); container.setGravity(Gravity.CENTER_HORIZONTAL); } mCandidateView = (CandidateView) container.findViewById(R.id.candidates); @@ -547,9 +561,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - final VoiceIMEConnector voiceIme = mVoiceConnector; - voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) - || Utils.isVisiblePasswordInputType(attribute.inputType)); + final VoiceProxy voiceIme = mVoiceProxy; + voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType)); initializeInputAttributes(attribute); @@ -603,17 +617,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { mIsSettingsSuggestionStripOn = true; // Make sure that passwords are not displayed in candidate view - if (Utils.isPasswordInputType(inputType) - || Utils.isVisiblePasswordInputType(inputType)) { + if (InputTypeCompatUtils.isPasswordInputType(inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; } - if (Utils.isEmailVariation(variation) + if (InputTypeCompatUtils.isEmailVariation(variation) || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { mAutoSpace = false; } else { mAutoSpace = true; } - if (Utils.isEmailVariation(variation)) { + if (InputTypeCompatUtils.isEmailVariation(variation)) { mIsSettingsSuggestionStripOn = false; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { mIsSettingsSuggestionStripOn = false; @@ -679,7 +693,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LatinImeLogger.commit(); mKeyboardSwitcher.onAutoCorrectionStateChanged(false); - mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging); + mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); KeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) inputView.closing(); @@ -700,7 +714,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onUpdateExtractedText(int token, ExtractedText text) { super.onUpdateExtractedText(token, text); - mVoiceConnector.showPunctuationHintIfNecessary(); + mVoiceProxy.showPunctuationHintIfNecessary(); } @Override @@ -721,7 +735,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + ", ce=" + candidatesEnd); } - mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart); + mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart); // If the current selection in the text view changes, we should // clear whatever candidate text we have. @@ -729,7 +743,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart; final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1; if (((mComposing.length() > 0 && mHasValidSuggestions) - || mVoiceConnector.isVoiceInputHighlighted()) + || mVoiceProxy.isVoiceInputHighlighted()) && (selectionChanged || candidatesCleared)) { if (candidatesCleared) { // If the composing span has been cleared, save the typed word in the history for @@ -745,7 +759,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ic != null) { ic.finishComposingText(); } - mVoiceConnector.setVoiceInputHighlighted(false); + mVoiceProxy.setVoiceInputHighlighted(false); } else if (!mHasValidSuggestions && !mJustAccepted) { if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) { if (TextEntryState.isAcceptedDefault()) @@ -825,7 +839,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mOptionsDialog.dismiss(); mOptionsDialog = null; } - mVoiceConnector.hideVoiceWindow(mConfigurationChanging); + mVoiceProxy.hideVoiceWindow(mConfigurationChanging); mWordHistory.clear(); super.hideWindow(); } @@ -902,8 +916,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (DEBUG) { Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height); } - outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; - outInsets.touchableRegion.set(x, y, width, height); + setTouchableRegionCompat(outInsets, x, y, width, height); } } @@ -1176,7 +1189,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onTextInput(CharSequence text) { - mVoiceConnector.commitVoiceInput(); + mVoiceProxy.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; abortRecorrection(false); @@ -1198,13 +1211,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void handleBackspace() { - if (mVoiceConnector.logAndRevertVoiceInput()) return; + if (mVoiceProxy.logAndRevertVoiceInput()) return; final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; ic.beginBatchEdit(); - mVoiceConnector.handleBackspace(); + mVoiceProxy.handleBackspace(); boolean deleteChar = false; if (mHasValidSuggestions) { @@ -1256,9 +1269,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void handleTab() { final int imeOptions = getCurrentInputEditorInfo().imeOptions; - final int navigationFlags = - EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; - if ((imeOptions & navigationFlags) == 0) { + if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) + && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) { sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); return; } @@ -1269,12 +1281,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // True if keyboard is in either chording shift or manual temporary upper case mode. final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase(); - if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0 + if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) && !isManualTemporaryUpperCase) { + EditorInfoCompatUtils.performEditorActionNext(ic); ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); - } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0 + } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) && isManualTemporaryUpperCase) { - ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); + EditorInfoCompatUtils.performEditorActionPrevious(ic); } } @@ -1288,7 +1301,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { - mVoiceConnector.handleCharacter(); + mVoiceProxy.handleCharacter(); if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) { abortRecorrection(false); @@ -1348,7 +1361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void handleSeparator(int primaryCode) { - mVoiceConnector.handleSeparator(); + mVoiceProxy.handleSeparator(); // Should dismiss the "Touch again to save" message when handling separator if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { @@ -1400,11 +1413,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen CharSequence typedWord = mWord.getTypedWord(); TextEntryState.backToAcceptedDefault(typedWord); if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { - if (ic != null) { - CorrectionInfo correctionInfo = new CorrectionInfo( - mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); - ic.commitCorrection(correctionInfo); - } + InputConnectionCompatUtils.commitCorrection( + ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); if (mCandidateView != null) mCandidateView.onAutoCorrectionInverted(mBestWord); } @@ -1418,7 +1428,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void handleClose() { commitTyped(getCurrentInputConnection()); - mVoiceConnector.handleClose(); + mVoiceProxy.handleClose(); requestHideSelf(0); LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) @@ -1436,7 +1446,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Make a copy of the CharSequence, since it is/could be a mutable CharSequence final String resultCopy = result.toString(); - TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, + WordAlternatives entry = new WordAlternatives(resultCopy, new WordComposer(mWord)); mWordHistory.add(entry); } @@ -1491,7 +1501,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void setSuggestions(SuggestedWords words) { - if (mVoiceConnector.getAndResetIsShowingHint()) { + if (mVoiceProxy.getAndResetIsShowingHint()) { setCandidatesView(mCandidateViewContainer); } @@ -1507,7 +1517,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void updateSuggestions() { // Check if we have a suggestion engine attached. if ((mSuggest == null || !isSuggestionsRequested()) - && !mVoiceConnector.isVoiceInputHighlighted()) { + && !mVoiceProxy.isVoiceInputHighlighted()) { return; } @@ -1602,7 +1612,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void pickSuggestionManually(int index, CharSequence suggestion) { SuggestedWords suggestions = mCandidateView.getSuggestions(); - mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); + mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); final boolean recorrecting = TextEntryState.isRecorrecting(); InputConnection ic = getCurrentInputConnection(); @@ -1706,7 +1716,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; InputConnection ic = getCurrentInputConnection(); if (ic != null) { - mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators); + mVoiceProxy.rememberReplacedWord(suggestion, mWordSeparators); ic.commitText(suggestion, 1); } saveWordInHistory(suggestion); @@ -1727,9 +1737,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Search old suggestions to suggest re-corrected suggestions. for (WordAlternatives entry : mWordHistory) { if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) { - if (entry instanceof TypedWordAlternatives) { - foundWord = ((TypedWordAlternatives) entry).word; - } + foundWord = entry.mWordComposer; alternatives = entry; break; } @@ -1749,7 +1757,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Found a match, show suggestions if (foundWord != null || alternatives != null) { if (alternatives == null) { - alternatives = new TypedWordAlternatives(touching.mWord, foundWord); + alternatives = new WordAlternatives(touching.mWord, foundWord); } showCorrections(alternatives); if (foundWord != null) { @@ -1763,7 +1771,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void setOldSuggestions() { - mVoiceConnector.setShowingVoiceSuggestions(false); + mVoiceProxy.setShowingVoiceSuggestions(false); if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { return; } @@ -1777,7 +1785,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (touching != null && touching.mWord.length() > 1) { ic.beginBatchEdit(); - if (!mVoiceConnector.applyVoiceAlternatives(touching) + if (!mVoiceProxy.applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { abortRecorrection(true); } else { @@ -1935,8 +1943,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Reload keyboard because the current language has been changed. mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), - mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); + mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(), + mVoiceProxy.isVoiceButtonOnPrimary()); initSuggest(); mKeyboardSwitcher.updateShiftState(); } @@ -2108,9 +2116,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void loadSettings(EditorInfo attribute) { // Get the settings preferences final SharedPreferences prefs = mPrefs; - Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); - mVibrateOn = vibrator != null && vibrator.hasVibrator() - && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); + final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator(); + mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, mResources.getBoolean(R.bool.config_default_sound_enabled)); @@ -2122,7 +2129,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs); loadAndSetAutoCorrectionThreshold(prefs); - mVoiceConnector.loadSettings(attribute, prefs); + mVoiceProxy.loadSettings(attribute, prefs); updateCorrectionMode(); updateAutoTextEnabled(); @@ -2233,13 +2240,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen di.dismiss(); switch (position) { case 0: - Intent intent = new Intent( - android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + Intent intent = CompatUtils.getInputLanguageSelectionIntent( + mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID, - mInputMethodId); startActivity(intent); break; case 1: @@ -2332,9 +2336,4 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); } - - @Override - public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { - SubtypeSwitcher.getInstance().updateSubtype(subtype); - } } diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java new file mode 100644 index 000000000..90726b0d8 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 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 android.content.Context; + +import java.util.Locale; + +class PrivateBinaryDictionaryGetter { + private PrivateBinaryDictionaryGetter() {} + public static AssetFileAddress getDictionaryFile(Locale locale, Context context) { + return null; + } +} diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 341d5add0..488ab09c3 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -16,17 +16,17 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.voice.VoiceIMEConnector; -import com.android.inputmethod.voice.VoiceInputLogger; +import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.compat.VibratorCompatWrapper; import android.app.AlertDialog; import android.app.Dialog; import android.app.backup.BackupManager; import android.content.DialogInterface; -import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.Vibrator; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; @@ -82,7 +82,7 @@ public class Settings extends PreferenceActivity private AlertDialog mDialog; - private VoiceInputLogger mLogger; + private VoiceProxy.VoiceLoggerWrapper mVoiceLogger; private boolean mOkClicked = false; private String mVoiceModeOff; @@ -111,7 +111,7 @@ public class Settings extends PreferenceActivity mVoiceModeOff = getString(R.string.voice_mode_off); mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff) .equals(mVoiceModeOff)); - mLogger = VoiceInputLogger.getLogger(this); + mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this); mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD); mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS); @@ -134,8 +134,7 @@ public class Settings extends PreferenceActivity generalSettings.removePreference(mVoicePreference); } - Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); - if (vibrator == null || !vibrator.hasVibrator()) { + if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) { generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); } @@ -184,7 +183,7 @@ public class Settings extends PreferenceActivity ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY)) .removePreference(mQuickFixes); } - if (!VoiceIMEConnector.VOICE_INSTALLED + if (!VoiceProxy.VOICE_INSTALLED || !SpeechRecognizer.isRecognitionAvailable(this)) { getPreferenceScreen().removePreference(mVoicePreference); } else { @@ -222,16 +221,9 @@ public class Settings extends PreferenceActivity @Override public boolean onPreferenceClick(Preference pref) { if (pref == mInputLanguageSelection) { - final String action; - if (android.os.Build.VERSION.SDK_INT - >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { - // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS - // TODO: Can this be a constant instead of literal String constant? - action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; - } else { - action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; - } - startActivity(new Intent(action)); + startActivity(CompatUtils.getInputLanguageSelectionIntent( + Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this), + getApplicationInfo().packageName), 0)); return true; } return false; @@ -277,10 +269,10 @@ public class Settings extends PreferenceActivity public void onClick(DialogInterface dialog, int whichButton) { if (whichButton == DialogInterface.BUTTON_NEGATIVE) { mVoicePreference.setValue(mVoiceModeOff); - mLogger.settingsWarningDialogCancel(); + mVoiceLogger.settingsWarningDialogCancel(); } else if (whichButton == DialogInterface.BUTTON_POSITIVE) { mOkClicked = true; - mLogger.settingsWarningDialogOk(); + mVoiceLogger.settingsWarningDialogOk(); } updateVoicePreference(); } @@ -311,7 +303,7 @@ public class Settings extends PreferenceActivity AlertDialog dialog = builder.create(); mDialog = dialog; dialog.setOnDismissListener(this); - mLogger.settingsWarningDialogShown(); + mVoiceLogger.settingsWarningDialogShown(); return dialog; default: Log.e(TAG, "unknown dialog " + id); @@ -321,7 +313,7 @@ public class Settings extends PreferenceActivity @Override public void onDismiss(DialogInterface dialog) { - mLogger.settingsWarningDialogDismissed(); + mVoiceLogger.settingsWarningDialogDismissed(); if (!mOkClicked) { // This assumes that onPreferenceClick gets called first, and this if the user // agreed after the warning, we set the mOkClicked value to true. @@ -331,10 +323,6 @@ public class Settings extends PreferenceActivity private void updateVoicePreference() { boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff); - if (isChecked) { - mLogger.voiceInputSettingEnabled(); - } else { - mLogger.voiceInputSettingDisabled(); - } + mVoiceLogger.voiceInputSettingEnabled(isChecked); } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index dc14d770a..10a3369ac 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,11 +16,12 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.LatinKeyboard; -import com.android.inputmethod.voice.SettingsUtil; -import com.android.inputmethod.voice.VoiceIMEConnector; -import com.android.inputmethod.voice.VoiceInput; import android.content.Context; import android.content.Intent; @@ -31,12 +32,10 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.AsyncTask; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import java.util.ArrayList; import java.util.Arrays; @@ -59,26 +58,27 @@ public class SubtypeSwitcher { private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); private /* final */ LatinIME mService; private /* final */ SharedPreferences mPrefs; - private /* final */ InputMethodManager mImm; + private /* final */ InputMethodManagerCompatWrapper mImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; private /* final */ boolean mConfigUseSpacebarLanguageSwitcher; - private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod = - new ArrayList<InputMethodSubtype>(); + private final ArrayList<InputMethodSubtypeCompatWrapper> + mEnabledKeyboardSubtypesOfCurrentInputMethod = + new ArrayList<InputMethodSubtypeCompatWrapper>(); private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>(); /*-----------------------------------------------------------*/ // Variants which should be changed only by reload functions. private boolean mNeedsToDisplayLanguage; private boolean mIsSystemLanguageSameAsInputLanguage; - private InputMethodInfo mShortcutInputMethodInfo; - private InputMethodSubtype mShortcutSubtype; - private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod; - private InputMethodSubtype mCurrentSubtype; + private InputMethodInfoCompatWrapper mShortcutInputMethodInfo; + private InputMethodSubtypeCompatWrapper mShortcutSubtype; + private List<InputMethodSubtypeCompatWrapper> mAllEnabledSubtypesOfCurrentInputMethod; + private InputMethodSubtypeCompatWrapper mCurrentSubtype; private Locale mSystemLocale; private Locale mInputLocale; private String mInputLocaleStr; - private VoiceInput mVoiceInput; + private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper; /*-----------------------------------------------------------*/ private boolean mIsNetworkConnected; @@ -102,7 +102,7 @@ public class SubtypeSwitcher { mService = service; mPrefs = prefs; mResources = service.getResources(); - mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mImm = InputMethodManagerCompatWrapper.getInstance(service); mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); @@ -113,7 +113,7 @@ public class SubtypeSwitcher { mCurrentSubtype = null; mAllEnabledSubtypesOfCurrentInputMethod = null; // TODO: Voice input should be created here - mVoiceInput = null; + mVoiceInputWrapper = null; mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean( R.bool.config_use_spacebar_language_switcher); if (mConfigUseSpacebarLanguageSwitcher) @@ -150,7 +150,7 @@ public class SubtypeSwitcher { null, true); mEnabledLanguagesOfCurrentInputMethod.clear(); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); - for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) { + for (InputMethodSubtypeCompatWrapper ims: mAllEnabledSubtypesOfCurrentInputMethod) { final String locale = ims.getLocale(); final String mode = ims.getMode(); mLocaleSplitter.setString(locale); @@ -184,10 +184,10 @@ public class SubtypeSwitcher { + ", " + mShortcutSubtype.getMode()))); } // TODO: Update an icon for shortcut IME - Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = + final Map<InputMethodInfoCompatWrapper, List<InputMethodSubtypeCompatWrapper>> shortcuts = mImm.getShortcutInputMethodsAndSubtypes(); - for (InputMethodInfo imi: shortcuts.keySet()) { - List<InputMethodSubtype> subtypes = shortcuts.get(imi); + for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) { + List<InputMethodSubtypeCompatWrapper> subtypes = shortcuts.get(imi); // TODO: Returns the first found IMI for now. Should handle all shortcuts as // appropriate. mShortcutInputMethodInfo = imi; @@ -206,11 +206,11 @@ public class SubtypeSwitcher { } // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. - public void updateSubtype(InputMethodSubtype newSubtype) { + public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) { final String newLocale; final String newMode; final String oldMode = getCurrentSubtypeMode(); - if (newSubtype == null) { + if (newSubtype == null || !newSubtype.hasOriginalObject()) { // 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."); @@ -243,30 +243,30 @@ public class SubtypeSwitcher { // We cancel its status when we change mode, while we reset otherwise. if (isKeyboardMode()) { if (modeChanged) { - if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { - mVoiceInput.cancel(); + if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) { + mVoiceInputWrapper.cancel(); } } if (modeChanged || languageChanged) { updateShortcutIME(); mService.onRefreshKeyboard(); } - } else if (isVoiceMode() && mVoiceInput != null) { + } else if (isVoiceMode() && mVoiceInputWrapper != null) { if (VOICE_MODE.equals(oldMode)) { - mVoiceInput.reset(); + mVoiceInputWrapper.reset(); } // If needsToShowWarningDialog is true, voice input need to show warning before // show recognition view. if (languageChanged || modeChanged - || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { + || VoiceProxy.getInstance().needsToShowWarningDialog()) { triggerVoiceIME(); } } else { Log.w(TAG, "Unknown subtype mode: " + newMode); - if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { + if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) { // We need to reset the voice input to release the resources and to reset its status // as it is not the current input mode. - mVoiceInput.reset(); + mVoiceInputWrapper.reset(); } } } @@ -308,20 +308,33 @@ public class SubtypeSwitcher { return; } final String imiId = mShortcutInputMethodInfo.getId(); - final InputMethodSubtype subtype = mShortcutSubtype; - new Thread("SwitchToShortcutIME") { + final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype; + new AsyncTask<Void, Void, Void>() { @Override - public void run() { + protected Void doInBackground(Void... params) { mImm.setInputMethodAndSubtype(token, imiId, subtype); + return null; } - }.start(); + + @Override + protected void onPostExecute(Void result) { + // Calls in this method need to be done in the same thread as the thread which + // called switchToShortcutIME(). + + // Notify an event that the current subtype was changed. This event will be + // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented + // when the API level is 10 or previous. + mService.notifyOnCurrentInputMethodSubtypeChanged(subtype); + } + }.execute(); } public Drawable getShortcutIcon() { return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype); } - private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) { + private Drawable getSubtypeIcon( + InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) { final PackageManager pm = mService.getPackageManager(); if (imi != null) { final String imiPackageName = imi.getPackageName(); @@ -361,8 +374,9 @@ public class SubtypeSwitcher { if (mShortcutSubtype == null) return true; final boolean allowsImplicitlySelectedSubtypes = true; - for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList( - mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { + for (final InputMethodSubtypeCompatWrapper enabledSubtype : + mImm.getEnabledInputMethodSubtypeList( + mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { if (enabledSubtype.equals(mShortcutSubtype)) return true; } @@ -507,9 +521,9 @@ public class SubtypeSwitcher { // Voice Input functions // /////////////////////////// - public boolean setVoiceInput(VoiceInput vi) { - if (mVoiceInput == null && vi != null) { - mVoiceInput = vi; + public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) { + if (mVoiceInputWrapper == null && vi != null) { + mVoiceInputWrapper = vi; if (isVoiceMode()) { if (DBG) { Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr()); @@ -527,7 +541,7 @@ public class SubtypeSwitcher { private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; - VoiceIMEConnector.getInstance().startListening(false, + VoiceProxy.getInstance().startListening(false, KeyboardSwitcher.getInstance().getInputView().getWindowToken()); } @@ -612,30 +626,14 @@ public class SubtypeSwitcher { } - // A list of locales which are supported by default for voice input, unless we get a - // different list from Gservices. - private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = - "en " + - "en_US " + - "en_GB " + - "en_AU " + - "en_CA " + - "en_IE " + - "en_IN " + - "en_NZ " + - "en_SG " + - "en_ZA "; - public boolean isVoiceSupported(String locale) { // Get the current list of supported locales and check the current locale against that // list. We cache this value so as not to check it every time the user starts a voice // input. Because this method is called by onStartInputView, this should mean that as // long as the locale doesn't change while the user is keeping the IME open, the // value should never be stale. - String supportedLocalesString = SettingsUtil.getSettingsString( - mService.getContentResolver(), - SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, - DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + String supportedLocalesString = VoiceProxy.getSupportedLocalesString( + mService.getContentResolver()); List<String> voiceInputSupportedLocales = Arrays.asList( supportedLocalesString.split("\\s+")); return voiceInputSupportedLocales.contains(locale); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 0de474e59..0cc9d4198 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -47,7 +48,7 @@ public class Suggest implements Dictionary.WordCallback { /** * Words that appear in both bigram and unigram data gets multiplier ranging from - * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from + * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from * bigram data. */ public static final double BIGRAM_MULTIPLIER_MIN = 1.2; @@ -92,13 +93,13 @@ public class Suggest implements Dictionary.WordCallback { private boolean mQuickFixesEnabled; private double mAutoCorrectionThreshold; - private int[] mPriorities = new int[mPrefMaxSuggestions]; - private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS]; + private int[] mScores = new int[mPrefMaxSuggestions]; + private int[] mBigramScores = new int[PREF_MAX_BIGRAMS]; private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>(); - private String mLowerOriginalWord; + private CharSequence mTypedWord; // TODO: Remove these member variables by passing more context to addWord() callback method private boolean mIsFirstCharCapitalized; @@ -106,12 +107,15 @@ public class Suggest implements Dictionary.WordCallback { private int mCorrectionMode = CORRECTION_BASIC; - public Suggest(Context context, int dictionaryResId) { - init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); + public Suggest(Context context, int dictionaryResId, Locale locale) { + init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale, + dictionaryResId)); } - /* package for test */ Suggest(File dictionary, long startOffset, long length) { - init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN)); + /* package for test */ Suggest(File dictionary, long startOffset, long length, + BinaryDictionary.Flag[] flagArray) { + init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN, + flagArray)); } private void init(Context context, BinaryDictionary mainDict) { @@ -128,6 +132,19 @@ public class Suggest implements Dictionary.WordCallback { initPool(); } + public void resetMainDict(Context context, int dictionaryResId, Locale locale) { + final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context, + DIC_MAIN, locale, dictionaryResId); + mMainDict = newMainDict; + if (null == newMainDict) { + mUnigramDictionaries.remove(DICT_KEY_MAIN); + mBigramDictionaries.remove(DICT_KEY_MAIN); + } else { + mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict); + mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict); + } + } + private void initPool() { for (int i = 0; i < mPrefMaxSuggestions; i++) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); @@ -207,8 +224,8 @@ public class Suggest implements Dictionary.WordCallback { throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); } mPrefMaxSuggestions = maxSuggestions; - mPriorities = new int[mPrefMaxSuggestions]; - mBigramPriorities = new int[PREF_MAX_BIGRAMS]; + mScores = new int[mPrefMaxSuggestions]; + mBigramScores = new int[PREF_MAX_BIGRAMS]; collectGarbage(mSuggestions, mPrefMaxSuggestions); while (mStringPool.size() < mPrefMaxSuggestions) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); @@ -256,25 +273,23 @@ public class Suggest implements Dictionary.WordCallback { mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); - Arrays.fill(mPriorities, 0); + Arrays.fill(mScores, 0); // Save a lowercase version of the original word CharSequence typedWord = wordComposer.getTypedWord(); if (typedWord != null) { final String typedWordString = typedWord.toString(); typedWord = typedWordString; - mLowerOriginalWord = typedWordString.toLowerCase(); // Treating USER_TYPED as UNIGRAM suggestion for logging now. LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED, Dictionary.DataType.UNIGRAM); - } else { - mLowerOriginalWord = ""; } + mTypedWord = typedWord; if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams - Arrays.fill(mBigramPriorities, 0); + Arrays.fill(mBigramScores, 0); collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); if (!TextUtils.isEmpty(prevWordForBigram)) { @@ -346,7 +361,7 @@ public class Suggest implements Dictionary.WordCallback { mWhiteListDictionary.getWhiteListedWord(typedWordString)); mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, - mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode, + mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText, whitelistedWord); if (autoText != null) { @@ -364,26 +379,25 @@ public class Suggest implements Dictionary.WordCallback { if (DBG) { double normalizedScore = mAutoCorrection.getNormalizedScore(); - ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList = + ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList = new ArrayList<SuggestedWords.SuggestedWordInfo>(); - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); - final int priorityLength = mPriorities.length; - for (int i = 0; i < priorityLength; ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); + for (int i = 0; i < mScores.length; ++i) { if (normalizedScore > 0) { - final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" + - normalizedScore + ")"; - frequencyInfoList.add( - new SuggestedWords.SuggestedWordInfo(priorityThreshold, false)); + final String scoreThreshold = String.format("%d (%4.2f)", mScores[i], + normalizedScore); + scoreInfoList.add( + new SuggestedWords.SuggestedWordInfo(scoreThreshold, false)); normalizedScore = 0.0; } else { - final String priority = Integer.toString(mPriorities[i]); - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false)); + final String score = Integer.toString(mScores[i]); + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false)); } } - for (int i = priorityLength; i < mSuggestions.size(); ++i) { - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); + for (int i = mScores.length; i < mSuggestions.size(); ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); } - return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); + return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList); } return new SuggestedWords.Builder().addWords(mSuggestions, null); } @@ -419,52 +433,37 @@ public class Suggest implements Dictionary.WordCallback { return mAutoCorrection.hasAutoCorrection(); } - private static boolean compareCaseInsensitive(final String lowerOriginalWord, - final char[] word, final int offset, final int length) { - final int originalLength = lowerOriginalWord.length(); - if (originalLength == length && Character.isUpperCase(word[offset])) { - for (int i = 0; i < originalLength; i++) { - if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { - return false; - } - } - return true; - } - return false; - } - @Override - public boolean addWord(final char[] word, final int offset, final int length, int freq, + public boolean addWord(final char[] word, final int offset, final int length, int score, final int dicTypeId, final Dictionary.DataType dataType) { Dictionary.DataType dataTypeForLog = dataType; - ArrayList<CharSequence> suggestions; - int[] priorities; - int prefMaxSuggestions; + final ArrayList<CharSequence> suggestions; + final int[] sortedScores; + final int prefMaxSuggestions; if(dataType == Dictionary.DataType.BIGRAM) { suggestions = mBigramSuggestions; - priorities = mBigramPriorities; + sortedScores = mBigramScores; prefMaxSuggestions = PREF_MAX_BIGRAMS; } else { suggestions = mSuggestions; - priorities = mPriorities; + sortedScores = mScores; prefMaxSuggestions = mPrefMaxSuggestions; } int pos = 0; // Check if it's the same word, only caps are different - if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { + if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) { // TODO: remove this surrounding if clause and move this logic to // getSuggestedWordBuilder. if (suggestions.size() > 0) { - final String currentHighestWordLowerCase = - suggestions.get(0).toString().toLowerCase(); + final String currentHighestWord = suggestions.get(0).toString(); // If the current highest word is also equal to typed word, we need to compare // frequency to determine the insertion position. This does not ensure strictly // correct ordering, but ensures the top score is on top which is enough for // removing duplicates correctly. - if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length) - && freq <= priorities[0]) { + if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length) + && score <= sortedScores[0]) { pos = 1; } } @@ -475,24 +474,24 @@ public class Suggest implements Dictionary.WordCallback { if(bigramSuggestion >= 0) { dataTypeForLog = Dictionary.DataType.BIGRAM; // turn freq from bigram into multiplier specified above - double multiplier = (((double) mBigramPriorities[bigramSuggestion]) + double multiplier = (((double) mBigramScores[bigramSuggestion]) / MAXIMUM_BIGRAM_FREQUENCY) * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN) + BIGRAM_MULTIPLIER_MIN; /* Log.d(TAG,"bigram num: " + bigramSuggestion + " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString() - + " currentPriority: " + freq + " bigramPriority: " - + mBigramPriorities[bigramSuggestion] + + " currentScore: " + score + " bigramScore: " + + mBigramScores[bigramSuggestion] + " multiplier: " + multiplier); */ - freq = (int)Math.round((freq * multiplier)); + score = (int)Math.round((score * multiplier)); } } - // Check the last one's priority and bail - if (priorities[prefMaxSuggestions - 1] >= freq) return true; + // Check the last one's score and bail + if (sortedScores[prefMaxSuggestions - 1] >= score) return true; while (pos < prefMaxSuggestions) { - if (priorities[pos] < freq - || (priorities[pos] == freq && length < suggestions.get(pos).length())) { + if (sortedScores[pos] < score + || (sortedScores[pos] == score && length < suggestions.get(pos).length())) { break; } pos++; @@ -502,8 +501,8 @@ public class Suggest implements Dictionary.WordCallback { return true; } - System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1); - priorities[pos] = freq; + System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1); + sortedScores[pos] = score; int poolSize = mStringPool.size(); StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 727e3f16d..35b2b123c 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,6 +16,9 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.keyboard.KeyboardId; import android.content.res.Resources; @@ -28,8 +31,6 @@ import android.text.InputType; import android.text.format.DateUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; import java.io.BufferedReader; import java.io.File; @@ -101,15 +102,15 @@ public class Utils { } } - public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) { + public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) { return imm.getEnabledInputMethodList().size() > 1 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled // input method subtype (The current IME should be LatinIME.) || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; } - public static String getInputMethodId(InputMethodManager imm, String packageName) { - for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { + public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) { + for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) { if (imi.getPackageName().equals(packageName)) return imi.getId(); } @@ -285,7 +286,7 @@ public class Utils { // In dictionary.cpp, getSuggestion() method, // suggestion scores are computed using the below formula. - // original score (called 'frequency') + // original score // := pow(mTypedLetterMultiplier (this is defined 2), // (the number of matched characters between typed word and suggested word)) // * (individual word's score which defined in the unigram dictionary, @@ -295,7 +296,7 @@ public class Utils { // (full match up to min(before.length(), after.length()) // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) // - If the word is a true full match except for differences in accents or - // capitalization, then treat it as if the frequency was 255. + // capitalization, then treat it as if the score was 255. // - If before.length() == after.length() // => multiply by mFullWordMultiplier (this is defined 2)) // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2 @@ -485,7 +486,7 @@ public class Utils { case InputType.TYPE_CLASS_PHONE: return KeyboardId.MODE_PHONE; case InputType.TYPE_CLASS_TEXT: - if (Utils.isEmailVariation(variation)) { + if (InputTypeCompatUtils.isEmailVariation(variation)) { return KeyboardId.MODE_EMAIL; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { return KeyboardId.MODE_URL; @@ -503,31 +504,6 @@ public class Utils { } } - public static boolean isEmailVariation(int variation) { - return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - } - - // Please refer to TextView.isPasswordInputType - public static boolean isPasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); - } - - // Please refer to TextView.isVisiblePasswordInputType - public static boolean isVisiblePasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - public static boolean containsInCsv(String key, String csv) { if (csv == null) return false; @@ -551,7 +527,9 @@ public class Utils { * @return main dictionary resource id */ public static int getMainDictionaryResourceId(Resources res) { - return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName()); + final String MAIN_DIC_NAME = "main"; + String packageName = LatinIME.class.getPackage().getName(); + return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName); } public static void loadNativeLibrary() { @@ -561,4 +539,66 @@ public class Utils { Log.e(TAG, "Could not load native library jni_latinime"); } } + + /** + * Returns true if a and b are equal ignoring the case of the character. + * @param a first character to check + * @param b second character to check + * @return {@code true} if a and b are equal, {@code false} otherwise. + */ + public static boolean equalsIgnoreCase(char a, char b) { + // Some language, such as Turkish, need testing both cases. + return a == b + || Character.toLowerCase(a) == Character.toLowerCase(b) + || Character.toUpperCase(a) == Character.toUpperCase(b); + } + + /** + * Returns true if a and b are equal ignoring the case of the characters, including if they are + * both null. + * @param a first CharSequence to check + * @param b second CharSequence to check + * @return {@code true} if a and b are equal, {@code false} otherwise. + */ + public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { + if (a == b) + return true; // including both a and b are null. + if (a == null || b == null) + return false; + final int length = a.length(); + if (length != b.length()) + return false; + for (int i = 0; i < length; i++) { + if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) + return false; + } + return true; + } + + /** + * Returns true if a and b are equal ignoring the case of the characters, including if a is null + * and b is zero length. + * @param a CharSequence to check + * @param b character array to check + * @param offset start offset of array b + * @param length length of characters in array b + * @return {@code true} if a and b are equal, {@code false} otherwise. + * @throws IndexOutOfBoundsException + * if {@code offset < 0 || length < 0 || offset + length > data.length}. + * @throws NullPointerException if {@code b == null}. + */ + public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) { + if (offset < 0 || length < 0 || length > b.length - offset) + throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset + + " length=" + length); + if (a == null) + return length == 0; // including a is null and b is zero length. + if (a.length() != length) + return false; + for (int i = 0; i < length; i++) { + if (!equalsIgnoreCase(a.charAt(i), b[offset + i])) + return false; + } + return true; + } } |