Merge "Simplify CursorAnchorInfoCompatWrapper"
commit
6547311865
|
@ -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;
|
||||||
}
|
}
|
||||||
|
if (instance == null) {
|
||||||
private Object mInstance;
|
return null;
|
||||||
|
|
||||||
private CursorAnchorInfoCompatWrapper(final Object instance) {
|
|
||||||
mInstance = instance;
|
|
||||||
}
|
}
|
||||||
|
return new RealWrapper(instance);
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -795,23 +795,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.
|
||||||
|
@ -1094,7 +1088,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
if (isFullscreenMode()) {
|
if (isFullscreenMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
|
mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.wrap(info));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue