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
main
Yohei Yukawa 2014-10-08 00:07:40 +09:00
parent 847735fdfa
commit dac49f9f6d
4 changed files with 145 additions and 100 deletions

View File

@ -16,13 +16,20 @@
package com.android.inputmethod.compat; package com.android.inputmethod.compat;
import android.annotation.TargetApi;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.RectF; 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. * 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; public static final int FLAG_IS_RTL = 0x04;
// Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). private CursorAnchorInfoCompatWrapper() {
private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass; // This class is not publicly instantiable.
private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod;
private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod;
private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod;
private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod;
private static final CompatUtils.ToObjectMethodWrapper<CharSequence> 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<Matrix> 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);
} }
@UsedForTesting @TargetApi(BuildCompatUtils.VERSION_CODES_LXX)
public boolean isAvailable() { @Nullable
return sCursorAnchorInfoClass.exists() && mInstance != null; public static CursorAnchorInfoCompatWrapper wrap(@Nullable final CursorAnchorInfo instance) {
} if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) {
return 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);
} }
return new CursorAnchorInfoCompatWrapper(instance); if (instance == null) {
} return null;
}
private static final class FakeHolder { return new RealWrapper(instance);
static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null);
}
@UsedForTesting
public static CursorAnchorInfoCompatWrapper getFake() {
return FakeHolder.sInstance;
} }
public int getSelectionStart() { public int getSelectionStart() {
return sGetSelectionStartMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public int getSelectionEnd() { public int getSelectionEnd() {
return sGetSelectionEndMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public CharSequence getComposingText() { public CharSequence getComposingText() {
return sGetComposingTextMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public int getComposingTextStart() { public int getComposingTextStart() {
return sGetComposingTextStartMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public Matrix getMatrix() { public Matrix getMatrix() {
return sGetMatrixMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public RectF getCharacterBounds(final int index) { public RectF getCharacterBounds(final int index) {
return sGetCharacterBoundsMethod.invoke(mInstance, index); throw new UnsupportedOperationException("not supported.");
} }
public int getCharacterBoundsFlags(final int index) { public int getCharacterBoundsFlags(final int index) {
return sGetCharacterBoundsFlagsMethod.invoke(mInstance, index); throw new UnsupportedOperationException("not supported.");
} }
public float getInsertionMarkerBaseline() { public float getInsertionMarkerBaseline() {
return sGetInsertionMarkerBaselineMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public float getInsertionMarkerBottom() { public float getInsertionMarkerBottom() {
return sGetInsertionMarkerBottomMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public float getInsertionMarkerHorizontal() { public float getInsertionMarkerHorizontal() {
return sGetInsertionMarkerHorizontalMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public float getInsertionMarkerTop() { public float getInsertionMarkerTop() {
return sGetInsertionMarkerTopMethod.invoke(mInstance); throw new UnsupportedOperationException("not supported.");
} }
public int getInsertionMarkerFlags() { 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();
}
} }
} }

View File

@ -30,6 +30,7 @@ import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/** /**
* A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class * 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 String mWaitingWord = null;
private int mWaitingCursorStart = INVALID_CURSOR_INDEX; private int mWaitingCursorStart = INVALID_CURSOR_INDEX;
private int mWaitingCursorEnd = INVALID_CURSOR_INDEX; private int mWaitingCursorEnd = INVALID_CURSOR_INDEX;
@Nullable
private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null; private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;
@Nonnull @Nonnull
@ -150,7 +152,7 @@ public class TextDecorator {
* mode.</p> * mode.</p>
* @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}. * @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; mCursorAnchorInfoWrapper = info;
// Do not use layoutLater() to minimize the latency. // Do not use layoutLater() to minimize the latency.
layoutImmediately(); layoutImmediately();
@ -182,7 +184,7 @@ public class TextDecorator {
private void layoutMain() { private void layoutMain() {
final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper; final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper;
if (info == null || !info.isAvailable()) { if (info == null) {
cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available."); cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available.");
return; return;
} }

View File

@ -791,23 +791,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
new ViewTreeObserver.OnPreDrawListener() { new ViewTreeObserver.OnPreDrawListener() {
@Override @Override
public boolean onPreDraw() { 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; 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 @Override
public void setCandidatesView(final View view) { public void setCandidatesView(final View view) {
// To ensure that CandidatesView will never be set. // To ensure that CandidatesView will never be set.
@ -1090,7 +1084,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (isFullscreenMode()) { if (isFullscreenMode()) {
return; return;
} }
mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info)); mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.wrap(info));
} }
/** /**

View File

@ -16,10 +16,12 @@
package com.android.inputmethod.latin.utils; package com.android.inputmethod.latin.utils;
import android.annotation.TargetApi;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.inputmethodservice.ExtractEditText; import android.inputmethodservice.ExtractEditText;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.text.Layout; import android.text.Layout;
import android.text.Spannable; import android.text.Spannable;
import android.view.View; import android.view.View;
@ -27,6 +29,12 @@ import android.view.ViewParent;
import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.CursorAnchorInfo;
import android.widget.TextView; 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 * 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 * {@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; 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}. * Returns {@link CursorAnchorInfo} from the given {@link TextView}.
* @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted. * @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 * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it
* is not feasible. * is not feasible.
*/ */
public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) { @TargetApi(BuildCompatUtils.VERSION_CODES_LXX)
Layout layout = textView.getLayout(); @Nullable
private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) {
final Layout layout = textView.getLayout();
if (layout == null) { if (layout == null) {
return null; return null;
} }