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:
parent
847735fdfa
commit
dac49f9f6d
4 changed files with 145 additions and 100 deletions
|
@ -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;
|
||||
@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;
|
||||
}
|
||||
|
||||
private Object mInstance;
|
||||
|
||||
private CursorAnchorInfoCompatWrapper(final Object instance) {
|
||||
mInstance = instance;
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@UsedForTesting
|
||||
public static CursorAnchorInfoCompatWrapper fromObject(final Object instance) {
|
||||
if (!sCursorAnchorInfoClass.exists()) {
|
||||
return new CursorAnchorInfoCompatWrapper(null);
|
||||
}
|
||||
return new CursorAnchorInfoCompatWrapper(instance);
|
||||
}
|
||||
|
||||
private static final class FakeHolder {
|
||||
static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null);
|
||||
}
|
||||
|
||||
@UsedForTesting
|
||||
public static CursorAnchorInfoCompatWrapper getFake() {
|
||||
return FakeHolder.sInstance;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue