diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index 4fd2a6936..6aa2736c1 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -127,4 +127,92 @@ public final class CompatUtils { 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 ToObjectMethodWrapper 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 { + 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/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java index 2cec14240..24eaec85c 100644 --- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java @@ -21,39 +21,8 @@ import android.graphics.RectF; import com.android.inputmethod.annotations.UsedForTesting; -import java.lang.reflect.Method; - @UsedForTesting public final class CursorAnchorInfoCompatWrapper { - // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). - private static Class getCursorAnchorInfoClass() { - try { - return Class.forName("android.view.inputmethod.CursorAnchorInfo"); - } catch (ClassNotFoundException e) { - return null; - } - } - private static final Class CLASS; - private static final Method METHOD_GET_CHARACTER_RECT; - private static final Method METHOD_GET_CHARACTER_RECT_FLAGS; - private static final Method METHOD_GET_COMPOSING_TEXT; - private static final Method METHOD_GET_COMPOSING_TEXT_START; - private static final Method METHOD_GET_MATRIX; - static { - CLASS = getCursorAnchorInfoClass(); - METHOD_GET_CHARACTER_RECT = CompatUtils.getMethod(CLASS, "getCharacterRect", int.class); - METHOD_GET_CHARACTER_RECT_FLAGS = CompatUtils.getMethod(CLASS, "getCharacterRectFlags", - int.class); - METHOD_GET_COMPOSING_TEXT = CompatUtils.getMethod(CLASS, "getComposingText"); - METHOD_GET_COMPOSING_TEXT_START = CompatUtils.getMethod(CLASS, "getComposingTextStart"); - METHOD_GET_MATRIX = CompatUtils.getMethod(CLASS, "getMatrix"); - } - - @UsedForTesting - public static boolean isAvailable() { - return CLASS != null; - } - public static final int CHARACTER_RECT_TYPE_MASK = 0x0f; /** @@ -83,6 +52,49 @@ public final class CursorAnchorInfoCompatWrapper { */ public static final int CHARACTER_RECT_TYPE_NOT_FEASIBLE = 4; + // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). + private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass; + private static final CompatUtils.ToObjectMethodWrapper sGetCharacterRectMethod; + private static final CompatUtils.ToIntMethodWrapper sGetCharacterRectFlagsMethod; + private static final CompatUtils.ToObjectMethodWrapper sGetComposingTextMethod; + private static final CompatUtils.ToIntMethodWrapper sGetComposingTextStartMethod; + private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBaselineMethod; + private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerBottomMethod; + private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerHorizontalMethod; + private static final CompatUtils.ToFloatMethodWrapper sGetInsertionMarkerTopMethod; + private static final CompatUtils.ToObjectMethodWrapper sGetMatrixMethod; + private static final CompatUtils.ToBooleanMethodWrapper sIsInsertionMarkerClippedMethod; + + private static int COMPOSING_TEXT_START_DEFAULT = -1; + static { + sCursorAnchorInfoClass = CompatUtils.getClassWrapper( + "android.view.inputmethod.CursorAnchorInfo"); + sGetCharacterRectMethod = sCursorAnchorInfoClass.getMethod( + "getCharacterRect", (RectF)null, int.class); + sGetCharacterRectFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getCharacterRectFlags", CHARACTER_RECT_TYPE_UNSPECIFIED, int.class); + sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod( + "getComposingText", (CharSequence)null); + sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getComposingTextStart", COMPOSING_TEXT_START_DEFAULT); + sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getInsertionMarkerBaseline", 0.0f); + sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getInsertionMarkerBottom", 0.0f); + sGetInsertionMarkerHorizontalMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getInsertionMarkerHorizontal", 0.0f); + sGetInsertionMarkerTopMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getInsertionMarkerTop", 0.0f); + sGetMatrixMethod = sCursorAnchorInfoClass.getMethod("getMatrix", (Matrix)null); + sIsInsertionMarkerClippedMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "isInsertionMarkerClipped", false); + } + + @UsedForTesting + public static boolean isAvailable() { + return sCursorAnchorInfoClass.exists(); + } + private Object mInstance; private CursorAnchorInfoCompatWrapper(final Object instance) { @@ -107,29 +119,42 @@ public final class CursorAnchorInfoCompatWrapper { } public CharSequence getComposingText() { - return (CharSequence) CompatUtils.invoke(mInstance, null, METHOD_GET_COMPOSING_TEXT); + return sGetComposingTextMethod.invoke(mInstance); } - private static int COMPOSING_TEXT_START_DEFAULT = -1; public int getComposingTextStart() { - if (mInstance == null || METHOD_GET_COMPOSING_TEXT_START == null) { - return COMPOSING_TEXT_START_DEFAULT; - } - return (int) CompatUtils.invoke(mInstance, null, METHOD_GET_COMPOSING_TEXT_START); + return sGetComposingTextStartMethod.invoke(mInstance); } public Matrix getMatrix() { - return (Matrix) CompatUtils.invoke(mInstance, null, METHOD_GET_MATRIX); + return sGetMatrixMethod.invoke(mInstance); } public RectF getCharacterRect(final int index) { - return (RectF) CompatUtils.invoke(mInstance, null, METHOD_GET_CHARACTER_RECT, index); + return sGetCharacterRectMethod.invoke(mInstance, index); } public int getCharacterRectFlags(final int index) { - if (mInstance == null || METHOD_GET_CHARACTER_RECT_FLAGS == null) { - return CHARACTER_RECT_TYPE_UNSPECIFIED; - } - return (int) CompatUtils.invoke(mInstance, null, METHOD_GET_CHARACTER_RECT_FLAGS, index); + return sGetCharacterRectFlagsMethod.invoke(mInstance, index); + } + + public float getInsertionMarkerBaseline() { + return sGetInsertionMarkerBaselineMethod.invoke(mInstance); + } + + public float getInsertionMarkerBottom() { + return sGetInsertionMarkerBottomMethod.invoke(mInstance); + } + + public float getInsertionMarkerHorizontal() { + return sGetInsertionMarkerHorizontalMethod.invoke(mInstance); + } + + public float getInsertionMarkerTop() { + return sGetInsertionMarkerTopMethod.invoke(mInstance); + } + + public boolean isInsertionMarkerClipped() { + return sIsInsertionMarkerClippedMethod.invoke(mInstance); } }