From dac49f9f6de83d9a0cb4d9e290340cde5297e0b3 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Wed, 8 Oct 2014 00:07:40 +0900 Subject: [PATCH] Simplify CursorAnchorInfoCompatWrapper With this CL, we will use CursorAnchorInfoCompatWrapper just to avoid unexpected NoClassDefFoundError due to the direct dependency CursorAnchorInfo class, which is available only on API level 21 and later. Change-Id: I254ff83f1ca41daa21d0666b5824af22ba529022 --- .../compat/CursorAnchorInfoCompatWrapper.java | 186 ++++++++++-------- .../inputmethod/keyboard/TextDecorator.java | 6 +- .../android/inputmethod/latin/LatinIME.java | 22 +-- .../latin/utils/CursorAnchorInfoUtils.java | 31 ++- 4 files changed, 145 insertions(+), 100 deletions(-) diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java index 5af31795c..f4f54b624 100644 --- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java @@ -16,13 +16,20 @@ package com.android.inputmethod.compat; +import android.annotation.TargetApi; import android.graphics.Matrix; import android.graphics.RectF; +import android.os.Build; +import android.view.inputmethod.CursorAnchorInfo; -import com.android.inputmethod.annotations.UsedForTesting; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -@UsedForTesting -public final class CursorAnchorInfoCompatWrapper { +/** + * 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. @@ -39,123 +46,138 @@ public final class CursorAnchorInfoCompatWrapper { */ public static final int FLAG_IS_RTL = 0x04; - // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). - private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass; - private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod; - private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod; - private static final CompatUtils.ToObjectMethodWrapper sGetCharacterBoundsMethod; - private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod; - 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.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod; - - private static int INVALID_TEXT_INDEX = -1; - static { - sCursorAnchorInfoClass = CompatUtils.getClassWrapper( - "android.view.inputmethod.CursorAnchorInfo"); - sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getSelectionStart", INVALID_TEXT_INDEX); - sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getSelectionEnd", INVALID_TEXT_INDEX); - sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod( - "getCharacterBounds", (RectF)null, int.class); - sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getCharacterBoundsFlags", 0, int.class); - sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod( - "getComposingText", (CharSequence)null); - sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getComposingTextStart", INVALID_TEXT_INDEX); - 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); - sGetInsertionMarkerFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getInsertionMarkerFlags", 0); + private CursorAnchorInfoCompatWrapper() { + // This class is not publicly instantiable. } - @UsedForTesting - public boolean isAvailable() { - return sCursorAnchorInfoClass.exists() && mInstance != null; - } - - private Object mInstance; - - private CursorAnchorInfoCompatWrapper(final Object instance) { - mInstance = instance; - } - - @UsedForTesting - public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) { - if (!sCursorAnchorInfoClass.exists()) { - return new CursorAnchorInfoCompatWrapper(null); + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + @Nullable + public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) { + if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + return null; } - return new CursorAnchorInfoCompatWrapper(instance); - } - - private static final class FakeHolder { - static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null); - } - - @UsedForTesting - public static CursorAnchorInfoCompatWrapper getFake() { - return FakeHolder.sInstance; + if (instance == null) { + return null; + } + return new RealWrapper(instance); } public int getSelectionStart() { - return sGetSelectionStartMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getSelectionEnd() { - return sGetSelectionEndMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public CharSequence getComposingText() { - return sGetComposingTextMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getComposingTextStart() { - return sGetComposingTextStartMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public Matrix getMatrix() { - return sGetMatrixMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public RectF getCharacterBounds(final int index) { - return sGetCharacterBoundsMethod.invoke(mInstance, index); + throw new UnsupportedOperationException("not supported."); } public int getCharacterBoundsFlags(final int index) { - return sGetCharacterBoundsFlagsMethod.invoke(mInstance, index); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerBaseline() { - return sGetInsertionMarkerBaselineMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerBottom() { - return sGetInsertionMarkerBottomMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerHorizontal() { - return sGetInsertionMarkerHorizontalMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public float getInsertionMarkerTop() { - return sGetInsertionMarkerTopMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); } public int getInsertionMarkerFlags() { - return sGetInsertionMarkerFlagsMethod.invoke(mInstance); + throw new UnsupportedOperationException("not supported."); + } + + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + 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/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java index c22717f95..ddc65bf36 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java @@ -30,6 +30,7 @@ import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class @@ -56,6 +57,7 @@ public class TextDecorator { private String mWaitingWord = null; private int mWaitingCursorStart = INVALID_CURSOR_INDEX; private int mWaitingCursorEnd = INVALID_CURSOR_INDEX; + @Nullable private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null; @Nonnull @@ -150,7 +152,7 @@ public class TextDecorator { * mode.

* @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}. */ - public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { + public void onUpdateCursorAnchorInfo(@Nullable final CursorAnchorInfoCompatWrapper info) { mCursorAnchorInfoWrapper = info; // Do not use layoutLater() to minimize the latency. layoutImmediately(); @@ -182,7 +184,7 @@ public class TextDecorator { private void layoutMain() { final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper; - if (info == null || !info.isAvailable()) { + if (info == null) { cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available."); return; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 77477d2d3..632ed2aac 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -791,23 +791,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - onExtractTextViewPreDraw(); + // CursorAnchorInfo is used on L and later. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.L) { + if (isFullscreenMode() && mExtractEditText != null) { + mInputLogic.onUpdateCursorAnchorInfo( + CursorAnchorInfoUtils.extractFromTextView(mExtractEditText)); + } + } return true; } }; - private void onExtractTextViewPreDraw() { - // CursorAnchorInfo is available on L and later. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.L) { - return; - } - if (!isFullscreenMode() || mExtractEditText == null) { - return; - } - final CursorAnchorInfo info = CursorAnchorInfoUtils.getCursorAnchorInfo(mExtractEditText); - mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); - } - @Override public void setCandidatesView(final View view) { // To ensure that CandidatesView will never be set. @@ -1090,7 +1084,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isFullscreenMode()) { return; } - mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); + mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.wrap(info)); } /** diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java index 9dc0524a2..e05618901 100644 --- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java @@ -16,10 +16,12 @@ package com.android.inputmethod.latin.utils; +import android.annotation.TargetApi; import android.graphics.Matrix; import android.graphics.Rect; import android.inputmethodservice.ExtractEditText; import android.inputmethodservice.InputMethodService; +import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.view.View; @@ -27,6 +29,12 @@ import android.view.ViewParent; import android.view.inputmethod.CursorAnchorInfo; import android.widget.TextView; +import com.android.inputmethod.compat.BuildCompatUtils; +import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * This class allows input methods to extract {@link CursorAnchorInfo} directly from the given * {@link TextView}. This is useful and even necessary to support full-screen mode where the default @@ -76,14 +84,33 @@ public final class CursorAnchorInfoUtils { return true; } + /** + * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}. + * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to + * be extracted. + * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout. + * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not + * ready to provide layout information. + */ + @Nullable + public static CursorAnchorInfoCompatWrapper extractFromTextView( + @Nonnull final TextView textView) { + if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + return null; + } + return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView)); + } + /** * Returns {@link CursorAnchorInfo} from the given {@link TextView}. * @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted. * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it * is not feasible. */ - public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) { - Layout layout = textView.getLayout(); + @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + @Nullable + private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) { + final Layout layout = textView.getLayout(); if (layout == null) { return null; }