aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/compat
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/org/kelar/inputmethod/compat')
-rw-r--r--java/src/org/kelar/inputmethod/compat/ActivityManagerCompatUtils.java46
-rw-r--r--java/src/org/kelar/inputmethod/compat/AppWorkaroundsHelper.java30
-rw-r--r--java/src/org/kelar/inputmethod/compat/AppWorkaroundsUtils.java60
-rw-r--r--java/src/org/kelar/inputmethod/compat/BuildCompatUtils.java36
-rw-r--r--java/src/org/kelar/inputmethod/compat/CharacterCompat.java47
-rw-r--r--java/src/org/kelar/inputmethod/compat/CompatUtils.java218
-rw-r--r--java/src/org/kelar/inputmethod/compat/ConnectivityManagerCompatUtils.java36
-rw-r--r--java/src/org/kelar/inputmethod/compat/CursorAnchorInfoCompatWrapper.java185
-rw-r--r--java/src/org/kelar/inputmethod/compat/EditorInfoCompatUtils.java98
-rw-r--r--java/src/org/kelar/inputmethod/compat/InputConnectionCompatUtils.java64
-rw-r--r--java/src/org/kelar/inputmethod/compat/InputMethodManagerCompatWrapper.java52
-rw-r--r--java/src/org/kelar/inputmethod/compat/InputMethodServiceCompatUtils.java37
-rw-r--r--java/src/org/kelar/inputmethod/compat/InputMethodSubtypeCompatUtils.java103
-rw-r--r--java/src/org/kelar/inputmethod/compat/IntentCompatUtils.java35
-rw-r--r--java/src/org/kelar/inputmethod/compat/LocaleListCompatUtils.java40
-rw-r--r--java/src/org/kelar/inputmethod/compat/LocaleSpanCompatUtils.java218
-rw-r--r--java/src/org/kelar/inputmethod/compat/LooperCompatUtils.java42
-rw-r--r--java/src/org/kelar/inputmethod/compat/NotificationCompatUtils.java83
-rw-r--r--java/src/org/kelar/inputmethod/compat/SettingsSecureCompatUtils.java36
-rw-r--r--java/src/org/kelar/inputmethod/compat/SuggestionSpanUtils.java121
-rw-r--r--java/src/org/kelar/inputmethod/compat/SuggestionsInfoCompatUtils.java47
-rw-r--r--java/src/org/kelar/inputmethod/compat/TextInfoCompatUtils.java67
-rw-r--r--java/src/org/kelar/inputmethod/compat/TextViewCompatUtils.java44
-rw-r--r--java/src/org/kelar/inputmethod/compat/UserDictionaryCompatUtils.java49
-rw-r--r--java/src/org/kelar/inputmethod/compat/UserManagerCompatUtils.java80
-rw-r--r--java/src/org/kelar/inputmethod/compat/ViewCompatUtils.java70
-rw-r--r--java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtils.java43
-rw-r--r--java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java72
28 files changed, 2059 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/compat/ActivityManagerCompatUtils.java b/java/src/org/kelar/inputmethod/compat/ActivityManagerCompatUtils.java
new file mode 100644
index 000000000..b39d274d7
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/ActivityManagerCompatUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.app.ActivityManager;
+import android.content.Context;
+
+import java.lang.reflect.Method;
+
+public class ActivityManagerCompatUtils {
+ private static final Object LOCK = new Object();
+ private static volatile Boolean sBoolean = null;
+ private static final Method METHOD_isLowRamDevice = CompatUtils.getMethod(
+ ActivityManager.class, "isLowRamDevice");
+
+ private ActivityManagerCompatUtils() {
+ // Do not instantiate this class.
+ }
+
+ public static boolean isLowRamDevice(Context context) {
+ if (sBoolean == null) {
+ synchronized(LOCK) {
+ if (sBoolean == null) {
+ final ActivityManager am =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ sBoolean = (Boolean)CompatUtils.invoke(am, false, METHOD_isLowRamDevice);
+ }
+ }
+ }
+ return sBoolean;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/AppWorkaroundsHelper.java b/java/src/org/kelar/inputmethod/compat/AppWorkaroundsHelper.java
new file mode 100644
index 000000000..42fbd62c4
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/AppWorkaroundsHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.content.pm.PackageInfo;
+
+@SuppressWarnings("unused")
+public class AppWorkaroundsHelper {
+ private AppWorkaroundsHelper() {
+ // This helper class is not publicly instantiable.
+ }
+
+ public static boolean evaluateIsBrokenByRecorrection(final PackageInfo info) {
+ return false;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/AppWorkaroundsUtils.java b/java/src/org/kelar/inputmethod/compat/AppWorkaroundsUtils.java
new file mode 100644
index 000000000..5e0187813
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/AppWorkaroundsUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.content.pm.PackageInfo;
+import android.os.Build.VERSION_CODES;
+
+/**
+ * A class to encapsulate work-arounds specific to particular apps.
+ */
+public class AppWorkaroundsUtils {
+ private final PackageInfo mPackageInfo; // May be null
+ private final boolean mIsBrokenByRecorrection;
+
+ public AppWorkaroundsUtils(final PackageInfo packageInfo) {
+ mPackageInfo = packageInfo;
+ mIsBrokenByRecorrection = AppWorkaroundsHelper.evaluateIsBrokenByRecorrection(
+ packageInfo);
+ }
+
+ public boolean isBrokenByRecorrection() {
+ return mIsBrokenByRecorrection;
+ }
+
+ public boolean isBeforeJellyBean() {
+ if (null == mPackageInfo || null == mPackageInfo.applicationInfo) {
+ return false;
+ }
+ return mPackageInfo.applicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN;
+ }
+
+ @Override
+ public String toString() {
+ if (null == mPackageInfo || null == mPackageInfo.applicationInfo) {
+ return "";
+ }
+ final StringBuilder s = new StringBuilder();
+ s.append("Target application : ")
+ .append(mPackageInfo.applicationInfo.name)
+ .append("\nPackage : ")
+ .append(mPackageInfo.applicationInfo.packageName)
+ .append("\nTarget app sdk version : ")
+ .append(mPackageInfo.applicationInfo.targetSdkVersion);
+ return s.toString();
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/BuildCompatUtils.java b/java/src/org/kelar/inputmethod/compat/BuildCompatUtils.java
new file mode 100644
index 000000000..c1080eb4c
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/BuildCompatUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.os.Build;
+
+public final class BuildCompatUtils {
+ private BuildCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static final boolean IS_RELEASE_BUILD = Build.VERSION.CODENAME.equals("REL");
+
+ /**
+ * The "effective" API version.
+ * {@link android.os.Build.VERSION#SDK_INT} if the platform is a release build.
+ * {@link android.os.Build.VERSION#SDK_INT} plus 1 if the platform is a development build.
+ */
+ public static final int EFFECTIVE_SDK_INT = IS_RELEASE_BUILD
+ ? Build.VERSION.SDK_INT
+ : Build.VERSION.SDK_INT + 1;
+}
diff --git a/java/src/org/kelar/inputmethod/compat/CharacterCompat.java b/java/src/org/kelar/inputmethod/compat/CharacterCompat.java
new file mode 100644
index 000000000..601a5dcf6
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/CharacterCompat.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import java.lang.reflect.Method;
+
+public final class CharacterCompat {
+ // Note that Character.isAlphabetic(int), has been introduced in API level 19
+ // (Build.VERSION_CODE.KITKAT).
+ private static final Method METHOD_isAlphabetic = CompatUtils.getMethod(
+ Character.class, "isAlphabetic", int.class);
+
+ private CharacterCompat() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isAlphabetic(final int code) {
+ if (METHOD_isAlphabetic != null) {
+ return (Boolean)CompatUtils.invoke(null, false, METHOD_isAlphabetic, code);
+ }
+ switch (Character.getType(code)) {
+ case Character.UPPERCASE_LETTER:
+ case Character.LOWERCASE_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ case Character.OTHER_LETTER:
+ case Character.LETTER_NUMBER:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/CompatUtils.java b/java/src/org/kelar/inputmethod/compat/CompatUtils.java
new file mode 100644
index 000000000..475685927
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/CompatUtils.java
@@ -0,0 +1,218 @@
+/*
+ * 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 org.kelar.inputmethod.compat;
+
+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;
+
+public final class CompatUtils {
+ private static final String TAG = CompatUtils.class.getSimpleName();
+
+ private CompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static Class<?> getClass(final String className) {
+ try {
+ return Class.forName(className);
+ } catch (final ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static Method getMethod(final Class<?> targetClass, final String name,
+ final Class<?>... parameterTypes) {
+ if (targetClass == null || TextUtils.isEmpty(name)) {
+ return null;
+ }
+ try {
+ return targetClass.getMethod(name, parameterTypes);
+ } catch (final SecurityException | NoSuchMethodException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public static Field getField(final Class<?> targetClass, final String name) {
+ if (targetClass == null || TextUtils.isEmpty(name)) {
+ return null;
+ }
+ try {
+ return targetClass.getField(name);
+ } catch (final SecurityException | NoSuchFieldException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public static Constructor<?> getConstructor(final Class<?> targetClass,
+ final Class<?> ... types) {
+ if (targetClass == null || types == null) {
+ return null;
+ }
+ try {
+ return targetClass.getConstructor(types);
+ } catch (final SecurityException | NoSuchMethodException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public static Object newInstance(final Constructor<?> constructor, final Object ... args) {
+ if (constructor == null) {
+ return null;
+ }
+ try {
+ return constructor.newInstance(args);
+ } catch (final InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ Log.e(TAG, "Exception in newInstance", e);
+ }
+ return null;
+ }
+
+ public static Object invoke(final Object receiver, final Object defaultValue,
+ final Method method, final Object... args) {
+ if (method == null) {
+ return defaultValue;
+ }
+ try {
+ return method.invoke(receiver, args);
+ } catch (final IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ Log.e(TAG, "Exception in invoke", e);
+ }
+ return defaultValue;
+ }
+
+ public static Object getFieldValue(final Object receiver, final Object defaultValue,
+ final Field field) {
+ if (field == null) {
+ return defaultValue;
+ }
+ try {
+ return field.get(receiver);
+ } catch (final IllegalAccessException | IllegalArgumentException e) {
+ Log.e(TAG, "Exception in getFieldValue", e);
+ }
+ return defaultValue;
+ }
+
+ public static void setFieldValue(final Object receiver, final Field field, final Object value) {
+ if (field == null) {
+ return;
+ }
+ try {
+ field.set(receiver, value);
+ } catch (final IllegalAccessException | IllegalArgumentException e) {
+ Log.e(TAG, "Exception in setFieldValue", e);
+ }
+ }
+
+ public static ClassWrapper getClassWrapper(final String className) {
+ return new ClassWrapper(getClass(className));
+ }
+
+ public static final class ClassWrapper {
+ private final Class<?> mClass;
+ public ClassWrapper(final Class<?> targetClass) {
+ mClass = targetClass;
+ }
+
+ public boolean exists() {
+ return mClass != null;
+ }
+
+ public <T> ToObjectMethodWrapper<T> getMethod(final String name,
+ final T defaultValue, final Class<?>... parameterTypes) {
+ return new ToObjectMethodWrapper<>(CompatUtils.getMethod(mClass, name, parameterTypes),
+ defaultValue);
+ }
+
+ public ToIntMethodWrapper getPrimitiveMethod(final String name, final int defaultValue,
+ final Class<?>... parameterTypes) {
+ return new ToIntMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes),
+ defaultValue);
+ }
+
+ public ToFloatMethodWrapper getPrimitiveMethod(final String name, final float defaultValue,
+ final Class<?>... parameterTypes) {
+ return new ToFloatMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes),
+ defaultValue);
+ }
+
+ public ToBooleanMethodWrapper getPrimitiveMethod(final String name,
+ final boolean defaultValue, final Class<?>... parameterTypes) {
+ return new ToBooleanMethodWrapper(CompatUtils.getMethod(mClass, name, parameterTypes),
+ defaultValue);
+ }
+ }
+
+ public static final class ToObjectMethodWrapper<T> {
+ private final Method mMethod;
+ private final T mDefaultValue;
+ public ToObjectMethodWrapper(final Method method, final T defaultValue) {
+ mMethod = method;
+ mDefaultValue = defaultValue;
+ }
+ @SuppressWarnings("unchecked")
+ public T invoke(final Object receiver, final Object... args) {
+ return (T) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args);
+ }
+ }
+
+ public static final class ToIntMethodWrapper {
+ private final Method mMethod;
+ private final int mDefaultValue;
+ public ToIntMethodWrapper(final Method method, final int defaultValue) {
+ mMethod = method;
+ mDefaultValue = defaultValue;
+ }
+ public int invoke(final Object receiver, final Object... args) {
+ return (int) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args);
+ }
+ }
+
+ public static final class ToFloatMethodWrapper {
+ private final Method mMethod;
+ private final float mDefaultValue;
+ public ToFloatMethodWrapper(final Method method, final float defaultValue) {
+ mMethod = method;
+ mDefaultValue = defaultValue;
+ }
+ public float invoke(final Object receiver, final Object... args) {
+ return (float) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args);
+ }
+ }
+
+ public static final class ToBooleanMethodWrapper {
+ private final Method mMethod;
+ private final boolean mDefaultValue;
+ public ToBooleanMethodWrapper(final Method method, final boolean defaultValue) {
+ mMethod = method;
+ mDefaultValue = defaultValue;
+ }
+ public boolean invoke(final Object receiver, final Object... args) {
+ return (boolean) CompatUtils.invoke(receiver, mDefaultValue, mMethod, args);
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/ConnectivityManagerCompatUtils.java b/java/src/org/kelar/inputmethod/compat/ConnectivityManagerCompatUtils.java
new file mode 100644
index 000000000..469c8d590
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/ConnectivityManagerCompatUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.net.ConnectivityManager;
+
+import java.lang.reflect.Method;
+
+public final class ConnectivityManagerCompatUtils {
+ // ConnectivityManager#isActiveNetworkMetered() has been introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ private static final Method METHOD_isActiveNetworkMetered = CompatUtils.getMethod(
+ ConnectivityManager.class, "isActiveNetworkMetered");
+
+ public static boolean isActiveNetworkMetered(final ConnectivityManager manager) {
+ return (Boolean)CompatUtils.invoke(manager,
+ // If the API telling whether the network is metered or not is not available,
+ // then the closest thing is "if it's a mobile connection".
+ manager.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_MOBILE,
+ METHOD_isActiveNetworkMetered);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/org/kelar/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
new file mode 100644
index 000000000..203f99109
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.inputmethod.CursorAnchorInfo;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A wrapper for {@link CursorAnchorInfo}, which has been introduced in API Level 21. You can use
+ * this wrapper to avoid direct dependency on newly introduced types.
+ */
+public class CursorAnchorInfoCompatWrapper {
+
+ /**
+ * The insertion marker or character bounds have at least one visible region.
+ */
+ public static final int FLAG_HAS_VISIBLE_REGION = 0x01;
+
+ /**
+ * The insertion marker or character bounds have at least one invisible (clipped) region.
+ */
+ public static final int FLAG_HAS_INVISIBLE_REGION = 0x02;
+
+ /**
+ * The insertion marker or character bounds is placed at right-to-left (RTL) character.
+ */
+ public static final int FLAG_IS_RTL = 0x04;
+
+ CursorAnchorInfoCompatWrapper() {
+ // This class is not publicly instantiable.
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Nullable
+ public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return null;
+ }
+ if (instance == null) {
+ return null;
+ }
+ return new RealWrapper(instance);
+ }
+
+ public int getSelectionStart() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public int getSelectionEnd() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public CharSequence getComposingText() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public int getComposingTextStart() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public Matrix getMatrix() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ @SuppressWarnings("unused")
+ public RectF getCharacterBounds(final int index) {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ @SuppressWarnings("unused")
+ public int getCharacterBoundsFlags(final int index) {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public float getInsertionMarkerBaseline() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public float getInsertionMarkerBottom() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public float getInsertionMarkerHorizontal() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public float getInsertionMarkerTop() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ public int getInsertionMarkerFlags() {
+ throw new UnsupportedOperationException("not supported.");
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static final class RealWrapper extends CursorAnchorInfoCompatWrapper {
+
+ @Nonnull
+ private final CursorAnchorInfo mInstance;
+
+ public RealWrapper(@Nonnull final CursorAnchorInfo info) {
+ mInstance = info;
+ }
+
+ @Override
+ public int getSelectionStart() {
+ return mInstance.getSelectionStart();
+ }
+
+ @Override
+ public int getSelectionEnd() {
+ return mInstance.getSelectionEnd();
+ }
+
+ @Override
+ public CharSequence getComposingText() {
+ return mInstance.getComposingText();
+ }
+
+ @Override
+ public int getComposingTextStart() {
+ return mInstance.getComposingTextStart();
+ }
+
+ @Override
+ public Matrix getMatrix() {
+ return mInstance.getMatrix();
+ }
+
+ @Override
+ public RectF getCharacterBounds(final int index) {
+ return mInstance.getCharacterBounds(index);
+ }
+
+ @Override
+ public int getCharacterBoundsFlags(final int index) {
+ return mInstance.getCharacterBoundsFlags(index);
+ }
+
+ @Override
+ public float getInsertionMarkerBaseline() {
+ return mInstance.getInsertionMarkerBaseline();
+ }
+
+ @Override
+ public float getInsertionMarkerBottom() {
+ return mInstance.getInsertionMarkerBottom();
+ }
+
+ @Override
+ public float getInsertionMarkerHorizontal() {
+ return mInstance.getInsertionMarkerHorizontal();
+ }
+
+ @Override
+ public float getInsertionMarkerTop() {
+ return mInstance.getInsertionMarkerTop();
+ }
+
+ @Override
+ public int getInsertionMarkerFlags() {
+ return mInstance.getInsertionMarkerFlags();
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/org/kelar/inputmethod/compat/EditorInfoCompatUtils.java
new file mode 100644
index 000000000..307f6891a
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/EditorInfoCompatUtils.java
@@ -0,0 +1,98 @@
+/*
+ * 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 org.kelar.inputmethod.compat;
+
+import android.view.inputmethod.EditorInfo;
+
+import java.lang.reflect.Field;
+import java.util.Locale;
+
+public final class EditorInfoCompatUtils {
+ // Note that EditorInfo.IME_FLAG_FORCE_ASCII has been introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
+ EditorInfo.class, "IME_FLAG_FORCE_ASCII");
+ private static final Integer OBJ_IME_FLAG_FORCE_ASCII = (Integer) CompatUtils.getFieldValue(
+ null /* receiver */, null /* defaultValue */, FIELD_IME_FLAG_FORCE_ASCII);
+ private static final Field FIELD_HINT_LOCALES = CompatUtils.getField(
+ EditorInfo.class, "hintLocales");
+
+ private EditorInfoCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean hasFlagForceAscii(final int imeOptions) {
+ if (OBJ_IME_FLAG_FORCE_ASCII == null) return false;
+ return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0;
+ }
+
+ public static String imeActionName(final int imeOptions) {
+ final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION;
+ switch (actionId) {
+ case EditorInfo.IME_ACTION_UNSPECIFIED:
+ return "actionUnspecified";
+ case EditorInfo.IME_ACTION_NONE:
+ return "actionNone";
+ case EditorInfo.IME_ACTION_GO:
+ return "actionGo";
+ case EditorInfo.IME_ACTION_SEARCH:
+ return "actionSearch";
+ case EditorInfo.IME_ACTION_SEND:
+ return "actionSend";
+ case EditorInfo.IME_ACTION_NEXT:
+ return "actionNext";
+ case EditorInfo.IME_ACTION_DONE:
+ return "actionDone";
+ case EditorInfo.IME_ACTION_PREVIOUS:
+ return "actionPrevious";
+ default:
+ return "actionUnknown(" + actionId + ")";
+ }
+ }
+
+ public static String imeOptionsName(final int imeOptions) {
+ final String action = imeActionName(imeOptions);
+ final StringBuilder flags = new StringBuilder();
+ if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ flags.append("flagNoEnterAction|");
+ }
+ if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
+ flags.append("flagNavigateNext|");
+ }
+ if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0) {
+ flags.append("flagNavigatePrevious|");
+ }
+ if (hasFlagForceAscii(imeOptions)) {
+ flags.append("flagForceAscii|");
+ }
+ return (action != null) ? flags + action : flags.toString();
+ }
+
+ public static Locale getPrimaryHintLocale(final EditorInfo editorInfo) {
+ if (editorInfo == null) {
+ return null;
+ }
+ final Object localeList = CompatUtils.getFieldValue(editorInfo, null, FIELD_HINT_LOCALES);
+ if (localeList == null) {
+ return null;
+ }
+ if (LocaleListCompatUtils.isEmpty(localeList)) {
+ return null;
+ }
+ return LocaleListCompatUtils.get(localeList, 0);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/org/kelar/inputmethod/compat/InputConnectionCompatUtils.java
new file mode 100644
index 000000000..e9eecc511
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/InputConnectionCompatUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+
+public final class InputConnectionCompatUtils {
+ private static final CompatUtils.ClassWrapper sInputConnectionType;
+ private static final CompatUtils.ToBooleanMethodWrapper sRequestCursorUpdatesMethod;
+ static {
+ sInputConnectionType = new CompatUtils.ClassWrapper(InputConnection.class);
+ sRequestCursorUpdatesMethod = sInputConnectionType.getPrimitiveMethod(
+ "requestCursorUpdates", false, int.class);
+ }
+
+ public static boolean isRequestCursorUpdatesAvailable() {
+ return sRequestCursorUpdatesMethod != null;
+ }
+
+ /**
+ * Local copies of some constants in InputConnection until the SDK becomes publicly available.
+ */
+ private static int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+ private static int CURSOR_UPDATE_MONITOR = 1 << 1;
+
+ private static boolean requestCursorUpdatesImpl(final InputConnection inputConnection,
+ final int cursorUpdateMode) {
+ if (!isRequestCursorUpdatesAvailable()) {
+ return false;
+ }
+ return sRequestCursorUpdatesMethod.invoke(inputConnection, cursorUpdateMode);
+ }
+
+ /**
+ * Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}.
+ * @param inputConnection the input connection to which the request is to be sent.
+ * @param enableMonitor {@code true} to request the editor to call back the method whenever the
+ * cursor/anchor position is changed.
+ * @param requestImmediateCallback {@code true} to request the editor to call back the method
+ * as soon as possible to notify the current cursor/anchor position to the input method.
+ * @return {@code false} if the request is not handled. Otherwise returns {@code true}.
+ */
+ public static boolean requestCursorUpdates(final InputConnection inputConnection,
+ final boolean enableMonitor, final boolean requestImmediateCallback) {
+ final int cursorUpdateMode = (enableMonitor ? CURSOR_UPDATE_MONITOR : 0)
+ | (requestImmediateCallback ? CURSOR_UPDATE_IMMEDIATE : 0);
+ return requestCursorUpdatesImpl(inputConnection, cursorUpdateMode);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/org/kelar/inputmethod/compat/InputMethodManagerCompatWrapper.java
new file mode 100644
index 000000000..ce081a3f8
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/InputMethodManagerCompatWrapper.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 org.kelar.inputmethod.compat;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.view.inputmethod.InputMethodManager;
+
+import java.lang.reflect.Method;
+
+public final class InputMethodManagerCompatWrapper {
+ // Note that InputMethodManager.switchToNextInputMethod() has been introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod(
+ InputMethodManager.class, "switchToNextInputMethod", IBinder.class, boolean.class);
+
+ // Note that InputMethodManager.shouldOfferSwitchingToNextInputMethod() has been introduced
+ // in API level 19 (Build.VERSION_CODES.KITKAT).
+ private static final Method METHOD_shouldOfferSwitchingToNextInputMethod =
+ CompatUtils.getMethod(InputMethodManager.class,
+ "shouldOfferSwitchingToNextInputMethod", IBinder.class);
+
+ public final InputMethodManager mImm;
+
+ public InputMethodManagerCompatWrapper(final Context context) {
+ mImm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+
+ public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
+ return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */,
+ METHOD_switchToNextInputMethod, token, onlyCurrentIme);
+ }
+
+ public boolean shouldOfferSwitchingToNextInputMethod(final IBinder token) {
+ return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */,
+ METHOD_shouldOfferSwitchingToNextInputMethod, token);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/org/kelar/inputmethod/compat/InputMethodServiceCompatUtils.java
new file mode 100644
index 000000000..8549f9e1b
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/InputMethodServiceCompatUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.kelar.inputmethod.compat;
+
+import android.inputmethodservice.InputMethodService;
+
+import java.lang.reflect.Method;
+
+public final class InputMethodServiceCompatUtils {
+ // Note that {@link InputMethodService#enableHardwareAcceleration} has been introduced
+ // in API level 17 (Build.VERSION_CODES.JELLY_BEAN_MR1).
+ private static final Method METHOD_enableHardwareAcceleration =
+ CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration");
+
+ private InputMethodServiceCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean enableHardwareAcceleration(final InputMethodService ims) {
+ return (Boolean)CompatUtils.invoke(ims, false /* defaultValue */,
+ METHOD_enableHardwareAcceleration);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/org/kelar/inputmethod/compat/InputMethodSubtypeCompatUtils.java
new file mode 100644
index 000000000..31d257ffe
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodSubtype;
+
+import org.kelar.inputmethod.annotations.UsedForTesting;
+import org.kelar.inputmethod.latin.RichInputMethodSubtype;
+import org.kelar.inputmethod.latin.common.Constants;
+import org.kelar.inputmethod.latin.common.LocaleUtils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
+
+public final class InputMethodSubtypeCompatUtils {
+ private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName();
+ // Note that InputMethodSubtype(int nameId, int iconId, String locale, String mode,
+ // String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id)
+ // has been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ private static final Constructor<?> CONSTRUCTOR_INPUT_METHOD_SUBTYPE =
+ CompatUtils.getConstructor(InputMethodSubtype.class,
+ int.class, int.class, String.class, String.class, String.class, boolean.class,
+ boolean.class, int.class);
+ static {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (CONSTRUCTOR_INPUT_METHOD_SUBTYPE == null) {
+ android.util.Log.w(TAG, "Warning!!! Constructor is not defined.");
+ }
+ }
+ }
+
+ // Note that {@link InputMethodSubtype#isAsciiCapable()} has been introduced in API level 19
+ // (Build.VERSION_CODE.KITKAT).
+ private static final Method METHOD_isAsciiCapable = CompatUtils.getMethod(
+ InputMethodSubtype.class, "isAsciiCapable");
+
+ private InputMethodSubtypeCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ @SuppressWarnings("deprecation")
+ @Nonnull
+ public static InputMethodSubtype newInputMethodSubtype(int nameId, int iconId, String locale,
+ String mode, String extraValue, boolean isAuxiliary,
+ boolean overridesImplicitlyEnabledSubtype, int id) {
+ if (CONSTRUCTOR_INPUT_METHOD_SUBTYPE == null
+ || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return new InputMethodSubtype(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype);
+ }
+ return (InputMethodSubtype) CompatUtils.newInstance(CONSTRUCTOR_INPUT_METHOD_SUBTYPE,
+ nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, id);
+ }
+
+ public static boolean isAsciiCapable(final RichInputMethodSubtype subtype) {
+ return isAsciiCapable(subtype.getRawSubtype());
+ }
+
+ public static boolean isAsciiCapable(final InputMethodSubtype subtype) {
+ return isAsciiCapableWithAPI(subtype)
+ || subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
+ }
+
+ // Note that InputMethodSubtype.getLanguageTag() is expected to be available in Android N+.
+ private static final Method GET_LANGUAGE_TAG =
+ CompatUtils.getMethod(InputMethodSubtype.class, "getLanguageTag");
+
+ public static Locale getLocaleObject(final InputMethodSubtype subtype) {
+ // Locale.forLanguageTag() is available only in Android L and later.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ final String languageTag = (String) CompatUtils.invoke(subtype, null, GET_LANGUAGE_TAG);
+ if (!TextUtils.isEmpty(languageTag)) {
+ return Locale.forLanguageTag(languageTag);
+ }
+ }
+ return LocaleUtils.constructLocaleFromString(subtype.getLocale());
+ }
+
+ @UsedForTesting
+ public static boolean isAsciiCapableWithAPI(final InputMethodSubtype subtype) {
+ return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/IntentCompatUtils.java b/java/src/org/kelar/inputmethod/compat/IntentCompatUtils.java
new file mode 100644
index 000000000..efe3e9ccc
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/IntentCompatUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.content.Intent;
+
+public final class IntentCompatUtils {
+ // Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17
+ // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ private static final String ACTION_USER_INITIALIZE =
+ (String)CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+ CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE"));
+
+ private IntentCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean is_ACTION_USER_INITIALIZE(final String action) {
+ return ACTION_USER_INITIALIZE != null && ACTION_USER_INITIALIZE.equals(action);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/LocaleListCompatUtils.java b/java/src/org/kelar/inputmethod/compat/LocaleListCompatUtils.java
new file mode 100644
index 000000000..44d485f13
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/LocaleListCompatUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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 org.kelar.inputmethod.compat;
+
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+public final class LocaleListCompatUtils {
+ private static final Class CLASS_LocaleList = CompatUtils.getClass("android.os.LocaleList");
+ private static final Method METHOD_get =
+ CompatUtils.getMethod(CLASS_LocaleList, "get", int.class);
+ private static final Method METHOD_isEmpty =
+ CompatUtils.getMethod(CLASS_LocaleList, "isEmpty");
+
+ private LocaleListCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isEmpty(final Object localeList) {
+ return (Boolean) CompatUtils.invoke(localeList, Boolean.FALSE, METHOD_isEmpty);
+ }
+
+ public static Locale get(final Object localeList, final int index) {
+ return (Locale) CompatUtils.invoke(localeList, null, METHOD_get, index);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/LocaleSpanCompatUtils.java b/java/src/org/kelar/inputmethod/compat/LocaleSpanCompatUtils.java
new file mode 100644
index 000000000..431507f89
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/LocaleSpanCompatUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.style.LocaleSpan;
+import android.util.Log;
+
+import org.kelar.inputmethod.annotations.UsedForTesting;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Locale;
+
+@UsedForTesting
+public final class LocaleSpanCompatUtils {
+ private static final String TAG = LocaleSpanCompatUtils.class.getSimpleName();
+
+ // Note that LocaleSpan(Locale locale) has been introduced in API level 17
+ // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ private static Class<?> getLocaleSpanClass() {
+ try {
+ return Class.forName("android.text.style.LocaleSpan");
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ private static final Class<?> LOCALE_SPAN_TYPE;
+ private static final Constructor<?> LOCALE_SPAN_CONSTRUCTOR;
+ private static final Method LOCALE_SPAN_GET_LOCALE;
+ static {
+ LOCALE_SPAN_TYPE = getLocaleSpanClass();
+ LOCALE_SPAN_CONSTRUCTOR = CompatUtils.getConstructor(LOCALE_SPAN_TYPE, Locale.class);
+ LOCALE_SPAN_GET_LOCALE = CompatUtils.getMethod(LOCALE_SPAN_TYPE, "getLocale");
+ }
+
+ @UsedForTesting
+ public static boolean isLocaleSpanAvailable() {
+ return (LOCALE_SPAN_CONSTRUCTOR != null && LOCALE_SPAN_GET_LOCALE != null);
+ }
+
+ @UsedForTesting
+ public static Object newLocaleSpan(final Locale locale) {
+ return CompatUtils.newInstance(LOCALE_SPAN_CONSTRUCTOR, locale);
+ }
+
+ @UsedForTesting
+ public static Locale getLocaleFromLocaleSpan(final Object localeSpan) {
+ return (Locale) CompatUtils.invoke(localeSpan, null, LOCALE_SPAN_GET_LOCALE);
+ }
+
+ /**
+ * Ensures that the specified range is covered with only one {@link LocaleSpan} with the given
+ * locale. If the region is already covered by one or more {@link LocaleSpan}, their ranges are
+ * updated so that each character has only one locale.
+ * @param spannable the spannable object to be updated.
+ * @param start the start index from which {@link LocaleSpan} is attached (inclusive).
+ * @param end the end index to which {@link LocaleSpan} is attached (exclusive).
+ * @param locale the locale to be attached to the specified range.
+ */
+ @UsedForTesting
+ public static void updateLocaleSpan(final Spannable spannable, final int start,
+ final int end, final Locale locale) {
+ if (end < start) {
+ Log.e(TAG, "Invalid range: start=" + start + " end=" + end);
+ return;
+ }
+ if (!isLocaleSpanAvailable()) {
+ return;
+ }
+ // A brief summary of our strategy;
+ // 1. Enumerate all LocaleSpans between [start - 1, end + 1].
+ // 2. For each LocaleSpan S:
+ // - Update the range of S so as not to cover [start, end] if S doesn't have the
+ // expected locale.
+ // - Mark S as "to be merged" if S has the expected locale.
+ // 3. Merge all the LocaleSpans that are marked as "to be merged" into one LocaleSpan.
+ // If no appropriate span is found, create a new one with newLocaleSpan method.
+ final int searchStart = Math.max(start - 1, 0);
+ final int searchEnd = Math.min(end + 1, spannable.length());
+ // LocaleSpans found in the target range. See the step 1 in the above comment.
+ final Object[] existingLocaleSpans = spannable.getSpans(searchStart, searchEnd,
+ LOCALE_SPAN_TYPE);
+ // LocaleSpans that are marked as "to be merged". See the step 2 in the above comment.
+ final ArrayList<Object> existingLocaleSpansToBeMerged = new ArrayList<>();
+ boolean isStartExclusive = true;
+ boolean isEndExclusive = true;
+ int newStart = start;
+ int newEnd = end;
+ for (final Object existingLocaleSpan : existingLocaleSpans) {
+ final Locale attachedLocale = getLocaleFromLocaleSpan(existingLocaleSpan);
+ if (!locale.equals(attachedLocale)) {
+ // This LocaleSpan does not have the expected locale. Update its range if it has
+ // an intersection with the range [start, end] (the first case of the step 2 in the
+ // above comment).
+ removeLocaleSpanFromRange(existingLocaleSpan, spannable, start, end);
+ continue;
+ }
+ final int spanStart = spannable.getSpanStart(existingLocaleSpan);
+ final int spanEnd = spannable.getSpanEnd(existingLocaleSpan);
+ if (spanEnd < spanStart) {
+ Log.e(TAG, "Invalid span: spanStart=" + spanStart + " spanEnd=" + spanEnd);
+ continue;
+ }
+ if (spanEnd < start || end < spanStart) {
+ // No intersection found.
+ continue;
+ }
+
+ // Here existingLocaleSpan has the expected locale and an intersection with the
+ // range [start, end] (the second case of the the step 2 in the above comment).
+ final int spanFlag = spannable.getSpanFlags(existingLocaleSpan);
+ if (spanStart < newStart) {
+ newStart = spanStart;
+ isStartExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (newEnd < spanEnd) {
+ newEnd = spanEnd;
+ isEndExclusive = ((spanFlag & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ existingLocaleSpansToBeMerged.add(existingLocaleSpan);
+ }
+
+ int originalLocaleSpanFlag = 0;
+ Object localeSpan = null;
+ if (existingLocaleSpansToBeMerged.isEmpty()) {
+ // If there is no LocaleSpan that is marked as to be merged, create a new one.
+ localeSpan = newLocaleSpan(locale);
+ } else {
+ // Reuse the first LocaleSpan to avoid unnecessary object instantiation.
+ localeSpan = existingLocaleSpansToBeMerged.get(0);
+ originalLocaleSpanFlag = spannable.getSpanFlags(localeSpan);
+ // No need to keep other instances.
+ for (int i = 1; i < existingLocaleSpansToBeMerged.size(); ++i) {
+ spannable.removeSpan(existingLocaleSpansToBeMerged.get(i));
+ }
+ }
+ final int localeSpanFlag = getSpanFlag(originalLocaleSpanFlag, isStartExclusive,
+ isEndExclusive);
+ spannable.setSpan(localeSpan, newStart, newEnd, localeSpanFlag);
+ }
+
+ private static void removeLocaleSpanFromRange(final Object localeSpan,
+ final Spannable spannable, final int removeStart, final int removeEnd) {
+ if (!isLocaleSpanAvailable()) {
+ return;
+ }
+ final int spanStart = spannable.getSpanStart(localeSpan);
+ final int spanEnd = spannable.getSpanEnd(localeSpan);
+ if (spanStart > spanEnd) {
+ Log.e(TAG, "Invalid span: spanStart=" + spanStart + " spanEnd=" + spanEnd);
+ return;
+ }
+ if (spanEnd < removeStart) {
+ // spanStart < spanEnd < removeStart < removeEnd
+ return;
+ }
+ if (removeEnd < spanStart) {
+ // spanStart < removeEnd < spanStart < spanEnd
+ return;
+ }
+ final int spanFlags = spannable.getSpanFlags(localeSpan);
+ if (spanStart < removeStart) {
+ if (removeEnd < spanEnd) {
+ // spanStart < removeStart < removeEnd < spanEnd
+ final Locale locale = getLocaleFromLocaleSpan(localeSpan);
+ spannable.setSpan(localeSpan, spanStart, removeStart, spanFlags);
+ final Object attionalLocaleSpan = newLocaleSpan(locale);
+ spannable.setSpan(attionalLocaleSpan, removeEnd, spanEnd, spanFlags);
+ return;
+ }
+ // spanStart < removeStart < spanEnd <= removeEnd
+ spannable.setSpan(localeSpan, spanStart, removeStart, spanFlags);
+ return;
+ }
+ if (removeEnd < spanEnd) {
+ // removeStart <= spanStart < removeEnd < spanEnd
+ spannable.setSpan(localeSpan, removeEnd, spanEnd, spanFlags);
+ return;
+ }
+ // removeStart <= spanStart < spanEnd < removeEnd
+ spannable.removeSpan(localeSpan);
+ }
+
+ private static int getSpanFlag(final int originalFlag,
+ final boolean isStartExclusive, final boolean isEndExclusive) {
+ return (originalFlag & ~Spanned.SPAN_POINT_MARK_MASK) |
+ getSpanPointMarkFlag(isStartExclusive, isEndExclusive);
+ }
+
+ private static int getSpanPointMarkFlag(final boolean isStartExclusive,
+ final boolean isEndExclusive) {
+ if (isStartExclusive) {
+ return isEndExclusive ? Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ : Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
+ }
+ return isEndExclusive ? Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ : Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/LooperCompatUtils.java b/java/src/org/kelar/inputmethod/compat/LooperCompatUtils.java
new file mode 100644
index 000000000..a48f4c1ab
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/LooperCompatUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.os.Looper;
+
+import java.lang.reflect.Method;
+
+/**
+ * Helper to call Looper#quitSafely, which was introduced in API
+ * level 18 (Build.VERSION_CODES.JELLY_BEAN_MR2).
+ *
+ * In unit tests, we create lots of instances of LatinIME, which means we need to clean up
+ * some Loopers lest we leak file descriptors. In normal use on a device though, this is never
+ * necessary (although it does not hurt).
+ */
+public final class LooperCompatUtils {
+ private static final Method METHOD_quitSafely = CompatUtils.getMethod(
+ Looper.class, "quitSafely");
+
+ public static void quitSafely(final Looper looper) {
+ if (null != METHOD_quitSafely) {
+ CompatUtils.invoke(looper, null /* default return value */, METHOD_quitSafely);
+ } else {
+ looper.quit();
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/NotificationCompatUtils.java b/java/src/org/kelar/inputmethod/compat/NotificationCompatUtils.java
new file mode 100644
index 000000000..7797edacf
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/NotificationCompatUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.app.Notification;
+import android.os.Build;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class NotificationCompatUtils {
+ // Note that TextInfo.getCharSequence() is supposed to be available in API level 21 and later.
+ private static final Method METHOD_setColor =
+ CompatUtils.getMethod(Notification.Builder.class, "setColor", int.class);
+ private static final Method METHOD_setVisibility =
+ CompatUtils.getMethod(Notification.Builder.class, "setVisibility", int.class);
+ private static final Method METHOD_setCategory =
+ CompatUtils.getMethod(Notification.Builder.class, "setCategory", String.class);
+ private static final Method METHOD_setPriority =
+ CompatUtils.getMethod(Notification.Builder.class, "setPriority", int.class);
+ private static final Method METHOD_build =
+ CompatUtils.getMethod(Notification.Builder.class, "build");
+ private static final Field FIELD_VISIBILITY_SECRET =
+ CompatUtils.getField(Notification.class, "VISIBILITY_SECRET");
+ private static final int VISIBILITY_SECRET = null == FIELD_VISIBILITY_SECRET ? 0
+ : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+ FIELD_VISIBILITY_SECRET);
+ private static final Field FIELD_CATEGORY_RECOMMENDATION =
+ CompatUtils.getField(Notification.class, "CATEGORY_RECOMMENDATION");
+ private static final String CATEGORY_RECOMMENDATION = null == FIELD_CATEGORY_RECOMMENDATION ? ""
+ : (String) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+ FIELD_CATEGORY_RECOMMENDATION);
+ private static final Field FIELD_PRIORITY_LOW =
+ CompatUtils.getField(Notification.class, "PRIORITY_LOW");
+ private static final int PRIORITY_LOW = null == FIELD_PRIORITY_LOW ? 0
+ : (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+ FIELD_PRIORITY_LOW);
+
+ private NotificationCompatUtils() {
+ // This class is non-instantiable.
+ }
+
+ // Sets the accent color
+ public static void setColor(final Notification.Builder builder, final int color) {
+ CompatUtils.invoke(builder, null, METHOD_setColor, color);
+ }
+
+ public static void setVisibilityToSecret(final Notification.Builder builder) {
+ CompatUtils.invoke(builder, null, METHOD_setVisibility, VISIBILITY_SECRET);
+ }
+
+ public static void setCategoryToRecommendation(final Notification.Builder builder) {
+ CompatUtils.invoke(builder, null, METHOD_setCategory, CATEGORY_RECOMMENDATION);
+ }
+
+ public static void setPriorityToLow(final Notification.Builder builder) {
+ CompatUtils.invoke(builder, null, METHOD_setPriority, PRIORITY_LOW);
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Notification build(final Notification.Builder builder) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // #build was added in API level 16, JELLY_BEAN
+ return (Notification) CompatUtils.invoke(builder, null, METHOD_build);
+ }
+ // #getNotification was deprecated in API level 16, JELLY_BEAN
+ return builder.getNotification();
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/SettingsSecureCompatUtils.java b/java/src/org/kelar/inputmethod/compat/SettingsSecureCompatUtils.java
new file mode 100644
index 000000000..2b47ee274
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/SettingsSecureCompatUtils.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 org.kelar.inputmethod.compat;
+
+import java.lang.reflect.Field;
+
+public final class SettingsSecureCompatUtils {
+ // Note that Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD has been introduced
+ // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
+ private static final Field FIELD_ACCESSIBILITY_SPEAK_PASSWORD = CompatUtils.getField(
+ android.provider.Settings.Secure.class, "ACCESSIBILITY_SPEAK_PASSWORD");
+
+ private SettingsSecureCompatUtils() {
+ // This class is non-instantiable.
+ }
+
+ /**
+ * Whether to speak passwords while in accessibility mode.
+ */
+ public static final String ACCESSIBILITY_SPEAK_PASSWORD = (String) CompatUtils.getFieldValue(
+ null /* receiver */, null /* defaultValue */, FIELD_ACCESSIBILITY_SPEAK_PASSWORD);
+}
diff --git a/java/src/org/kelar/inputmethod/compat/SuggestionSpanUtils.java b/java/src/org/kelar/inputmethod/compat/SuggestionSpanUtils.java
new file mode 100644
index 000000000..080d4754b
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/SuggestionSpanUtils.java
@@ -0,0 +1,121 @@
+/*
+ * 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 org.kelar.inputmethod.compat;
+
+import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+
+import org.kelar.inputmethod.annotations.UsedForTesting;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.common.LocaleUtils;
+import org.kelar.inputmethod.latin.define.DebugFlags;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public final class SuggestionSpanUtils {
+ // Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced
+ // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
+ private static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField(
+ SuggestionSpan.class, "FLAG_AUTO_CORRECTION");
+ private static final Integer OBJ_FLAG_AUTO_CORRECTION = (Integer) CompatUtils.getFieldValue(
+ null /* receiver */, null /* defaultValue */, FIELD_FLAG_AUTO_CORRECTION);
+
+ static {
+ if (DebugFlags.DEBUG_ENABLED) {
+ if (OBJ_FLAG_AUTO_CORRECTION == null) {
+ throw new RuntimeException("Field is accidentially null.");
+ }
+ }
+ }
+
+ private SuggestionSpanUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ @UsedForTesting
+ public static CharSequence getTextWithAutoCorrectionIndicatorUnderline(
+ final Context context, final String text, @Nonnull final Locale locale) {
+ if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) {
+ return text;
+ }
+ final Spannable spannable = new SpannableString(text);
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale,
+ new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, null);
+ spannable.setSpan(suggestionSpan, 0, text.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ return spannable;
+ }
+
+ @UsedForTesting
+ public static CharSequence getTextWithSuggestionSpan(final Context context,
+ final String pickedWord, final SuggestedWords suggestedWords, final Locale locale) {
+ if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
+ || suggestedWords.isPrediction() || suggestedWords.isPunctuationSuggestions()) {
+ return pickedWord;
+ }
+
+ final ArrayList<String> suggestionsList = new ArrayList<>();
+ for (int i = 0; i < suggestedWords.size(); ++i) {
+ if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) {
+ break;
+ }
+ final SuggestedWordInfo info = suggestedWords.getInfo(i);
+ if (info.isKindOf(SuggestedWordInfo.KIND_PREDICTION)) {
+ continue;
+ }
+ final String word = suggestedWords.getWord(i);
+ if (!TextUtils.equals(pickedWord, word)) {
+ suggestionsList.add(word.toString());
+ }
+ }
+ final SuggestionSpan suggestionSpan = new SuggestionSpan(context, locale,
+ suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */, null);
+ final Spannable spannable = new SpannableString(pickedWord);
+ spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return spannable;
+ }
+
+ /**
+ * Returns first {@link Locale} found in the given array of {@link SuggestionSpan}.
+ * @param suggestionSpans the array of {@link SuggestionSpan} to be examined.
+ * @return the first {@link Locale} found in {@code suggestionSpans}. {@code null} when not
+ * found.
+ */
+ @UsedForTesting
+ @Nullable
+ public static Locale findFirstLocaleFromSuggestionSpans(
+ final SuggestionSpan[] suggestionSpans) {
+ for (final SuggestionSpan suggestionSpan : suggestionSpans) {
+ final String localeString = suggestionSpan.getLocale();
+ if (TextUtils.isEmpty(localeString)) {
+ continue;
+ }
+ return LocaleUtils.constructLocaleFromString(localeString);
+ }
+ return null;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/org/kelar/inputmethod/compat/SuggestionsInfoCompatUtils.java
new file mode 100644
index 000000000..fd7210726
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/SuggestionsInfoCompatUtils.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 org.kelar.inputmethod.compat;
+
+import android.view.textservice.SuggestionsInfo;
+
+import java.lang.reflect.Field;
+
+public final class SuggestionsInfoCompatUtils {
+ // Note that SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS has been introduced
+ // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
+ private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS =
+ CompatUtils.getField(SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS");
+ private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS =
+ (Integer) CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
+ FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS);
+ private static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS =
+ OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS != null
+ ? OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS : 0;
+
+ private SuggestionsInfoCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ /**
+ * Returns the flag value of the attributes of the suggestions that can be obtained by
+ * {@link SuggestionsInfo#getSuggestionsAttributes()}: this tells that the text service thinks
+ * the result suggestions include highly recommended ones.
+ */
+ public static int getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() {
+ return RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/TextInfoCompatUtils.java b/java/src/org/kelar/inputmethod/compat/TextInfoCompatUtils.java
new file mode 100644
index 000000000..d5b97b75e
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/TextInfoCompatUtils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.view.textservice.TextInfo;
+
+import org.kelar.inputmethod.annotations.UsedForTesting;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+@UsedForTesting
+public final class TextInfoCompatUtils {
+ // Note that TextInfo.getCharSequence() is supposed to be available in API level 21 and later.
+ private static final Method TEXT_INFO_GET_CHAR_SEQUENCE =
+ CompatUtils.getMethod(TextInfo.class, "getCharSequence");
+ private static final Constructor<?> TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE =
+ CompatUtils.getConstructor(TextInfo.class, CharSequence.class, int.class, int.class,
+ int.class, int.class);
+
+ @UsedForTesting
+ public static boolean isCharSequenceSupported() {
+ return TEXT_INFO_GET_CHAR_SEQUENCE != null &&
+ TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE != null;
+ }
+
+ @UsedForTesting
+ public static TextInfo newInstance(CharSequence charSequence, int start, int end, int cookie,
+ int sequenceNumber) {
+ if (TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE != null) {
+ return (TextInfo) CompatUtils.newInstance(TEXT_INFO_CONSTRUCTOR_FOR_CHAR_SEQUENCE,
+ charSequence, start, end, cookie, sequenceNumber);
+ }
+ return new TextInfo(charSequence.subSequence(start, end).toString(), cookie,
+ sequenceNumber);
+ }
+
+ /**
+ * Returns the result of {@link TextInfo#getCharSequence()} when available. Otherwise returns
+ * the result of {@link TextInfo#getText()} as fall back.
+ * @param textInfo the instance for which {@link TextInfo#getCharSequence()} or
+ * {@link TextInfo#getText()} is called.
+ * @return the result of {@link TextInfo#getCharSequence()} when available. Otherwise returns
+ * the result of {@link TextInfo#getText()} as fall back. If {@code textInfo} is {@code null},
+ * returns {@code null}.
+ */
+ @UsedForTesting
+ public static CharSequence getCharSequenceOrString(final TextInfo textInfo) {
+ final CharSequence defaultValue = (textInfo == null ? null : textInfo.getText());
+ return (CharSequence) CompatUtils.invoke(textInfo, defaultValue,
+ TEXT_INFO_GET_CHAR_SEQUENCE);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/TextViewCompatUtils.java b/java/src/org/kelar/inputmethod/compat/TextViewCompatUtils.java
new file mode 100644
index 000000000..dfad0e3a8
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/TextViewCompatUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.graphics.drawable.Drawable;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+public final class TextViewCompatUtils {
+ // Note that TextView.setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable,Drawable,
+ // Drawable,Drawable) has been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ private static final Method METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds =
+ CompatUtils.getMethod(TextView.class, "setCompoundDrawablesRelativeWithIntrinsicBounds",
+ Drawable.class, Drawable.class, Drawable.class, Drawable.class);
+
+ private TextViewCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static void setCompoundDrawablesRelativeWithIntrinsicBounds(final TextView textView,
+ final Drawable start, final Drawable top, final Drawable end, final Drawable bottom) {
+ if (METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds == null) {
+ textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+ return;
+ }
+ CompatUtils.invoke(textView, null, METHOD_setCompoundDrawablesRelativeWithIntrinsicBounds,
+ start, top, end, bottom);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/org/kelar/inputmethod/compat/UserDictionaryCompatUtils.java
new file mode 100644
index 000000000..0a4635c86
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.provider.UserDictionary;
+
+import java.util.Locale;
+
+public final class UserDictionaryCompatUtils {
+ @SuppressWarnings("deprecation")
+ public static void addWord(final Context context, final String word,
+ final int freq, final String shortcut, final Locale locale) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ addWordWithShortcut(context, word, freq, shortcut, locale);
+ return;
+ }
+ // Fall back to the pre-JellyBean method.
+ final Locale currentLocale = context.getResources().getConfiguration().locale;
+ final int localeType = currentLocale.equals(locale)
+ ? UserDictionary.Words.LOCALE_TYPE_CURRENT : UserDictionary.Words.LOCALE_TYPE_ALL;
+ UserDictionary.Words.addWord(context, word, freq, localeType);
+ }
+
+ // {@link UserDictionary.Words#addWord(Context,String,int,String,Locale)} was introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private static void addWordWithShortcut(final Context context, final String word,
+ final int freq, final String shortcut, final Locale locale) {
+ UserDictionary.Words.addWord(context, word, freq, shortcut, locale);
+ }
+}
+
diff --git a/java/src/org/kelar/inputmethod/compat/UserManagerCompatUtils.java b/java/src/org/kelar/inputmethod/compat/UserManagerCompatUtils.java
new file mode 100644
index 000000000..f72e28726
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/UserManagerCompatUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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 org.kelar.inputmethod.compat;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.UserManager;
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Method;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * A temporary solution until {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library
+ * becomes publicly available.
+ */
+public final class UserManagerCompatUtils {
+ private static final Method METHOD_isUserUnlocked;
+
+ static {
+ // We do not try to search the method in Android M and prior.
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.M) {
+ METHOD_isUserUnlocked = null;
+ } else {
+ METHOD_isUserUnlocked = CompatUtils.getMethod(UserManager.class, "isUserUnlocked");
+ }
+ }
+
+ private UserManagerCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static final int LOCK_STATE_UNKNOWN = 0;
+ public static final int LOCK_STATE_UNLOCKED = 1;
+ public static final int LOCK_STATE_LOCKED = 2;
+
+ @Retention(SOURCE)
+ @IntDef({LOCK_STATE_UNKNOWN, LOCK_STATE_UNLOCKED, LOCK_STATE_LOCKED})
+ public @interface LockState {}
+
+ /**
+ * Check if the calling user is running in an "unlocked" state. A user is unlocked only after
+ * they've entered their credentials (such as a lock pattern or PIN), and credential-encrypted
+ * private app data storage is available.
+ * @param context context from which {@link UserManager} should be obtained.
+ * @return One of {@link LockState}.
+ */
+ @LockState
+ public static int getUserLockState(final Context context) {
+ if (METHOD_isUserUnlocked == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ final Boolean result =
+ (Boolean) CompatUtils.invoke(userManager, null, METHOD_isUserUnlocked);
+ if (result == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ return result ? LOCK_STATE_UNLOCKED : LOCK_STATE_LOCKED;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/ViewCompatUtils.java b/java/src/org/kelar/inputmethod/compat/ViewCompatUtils.java
new file mode 100644
index 000000000..9b91897e5
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/ViewCompatUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.compat;
+
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+// TODO: Use {@link androidx.core.view.ViewCompat} instead of this utility class.
+// Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)}
+// are missing from android-support-v4 static library in KitKat SDK.
+public final class ViewCompatUtils {
+ // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int) have been
+ // introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+ private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod(
+ View.class, "getPaddingEnd");
+ private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod(
+ View.class, "setPaddingRelative",
+ int.class, int.class, int.class, int.class);
+ // Note that View.setTextAlignment(int) has been introduced in API level 17.
+ private static final Method METHOD_setTextAlignment = CompatUtils.getMethod(
+ View.class, "setTextAlignment", int.class);
+
+ private ViewCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static int getPaddingEnd(final View view) {
+ if (METHOD_getPaddingEnd == null) {
+ return view.getPaddingRight();
+ }
+ return (Integer)CompatUtils.invoke(view, 0, METHOD_getPaddingEnd);
+ }
+
+ public static void setPaddingRelative(final View view, final int start, final int top,
+ final int end, final int bottom) {
+ if (METHOD_setPaddingRelative == null) {
+ view.setPadding(start, top, end, bottom);
+ return;
+ }
+ CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom);
+ }
+
+ // These TEXT_ALIGNMENT_* constants have been introduced in API 17.
+ public static final int TEXT_ALIGNMENT_INHERIT = 0;
+ public static final int TEXT_ALIGNMENT_GRAVITY = 1;
+ public static final int TEXT_ALIGNMENT_TEXT_START = 2;
+ public static final int TEXT_ALIGNMENT_TEXT_END = 3;
+ public static final int TEXT_ALIGNMENT_CENTER = 4;
+ public static final int TEXT_ALIGNMENT_VIEW_START = 5;
+ public static final int TEXT_ALIGNMENT_VIEW_END = 6;
+
+ public static void setTextAlignment(final View view, final int textAlignment) {
+ CompatUtils.invoke(view, null, METHOD_setTextAlignment, textAlignment);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtils.java b/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtils.java
new file mode 100644
index 000000000..64f4230ba
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.view.View;
+
+public class ViewOutlineProviderCompatUtils {
+ private ViewOutlineProviderCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public interface InsetsUpdater {
+ public void setInsets(final InputMethodService.Insets insets);
+ }
+
+ private static final InsetsUpdater EMPTY_INSETS_UPDATER = new InsetsUpdater() {
+ @Override
+ public void setInsets(final InputMethodService.Insets insets) {}
+ };
+
+ public static InsetsUpdater setInsetsOutlineProvider(final View view) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return EMPTY_INSETS_UPDATER;
+ }
+ return ViewOutlineProviderCompatUtilsLXX.setInsetsOutlineProvider(view);
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java b/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java
new file mode 100644
index 000000000..8049d7d04
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/compat/ViewOutlineProviderCompatUtilsLXX.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 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 org.kelar.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.graphics.Outline;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import org.kelar.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class ViewOutlineProviderCompatUtilsLXX {
+ private ViewOutlineProviderCompatUtilsLXX() {
+ // This utility class is not publicly instantiable.
+ }
+
+ static InsetsUpdater setInsetsOutlineProvider(final View view) {
+ final InsetsOutlineProvider provider = new InsetsOutlineProvider(view);
+ view.setOutlineProvider(provider);
+ return provider;
+ }
+
+ private static class InsetsOutlineProvider extends ViewOutlineProvider
+ implements InsetsUpdater {
+ private final View mView;
+ private static final int NO_DATA = -1;
+ private int mLastVisibleTopInsets = NO_DATA;
+
+ public InsetsOutlineProvider(final View view) {
+ mView = view;
+ view.setOutlineProvider(this);
+ }
+
+ @Override
+ public void setInsets(final InputMethodService.Insets insets) {
+ final int visibleTopInsets = insets.visibleTopInsets;
+ if (mLastVisibleTopInsets != visibleTopInsets) {
+ mLastVisibleTopInsets = visibleTopInsets;
+ mView.invalidateOutline();
+ }
+ }
+
+ @Override
+ public void getOutline(final View view, final Outline outline) {
+ if (mLastVisibleTopInsets == NO_DATA) {
+ // Call default implementation.
+ ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
+ return;
+ }
+ // TODO: Revisit this when floating/resize keyboard is supported.
+ outline.setRect(
+ view.getLeft(), mLastVisibleTopInsets, view.getRight(), view.getBottom());
+ }
+ }
+}