Merge "Simplify CursorAnchorInfoCompatWrapper"

main
Yohei Yukawa 2014-10-09 08:13:50 +00:00 committed by Android (Google) Code Review
commit 6547311865
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

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

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