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
This commit is contained in:
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;
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<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);
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();
}
}
}

View file

@ -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.</p>
* @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;
}

View file

@ -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));
}
/**

View file

@ -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;
}