Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dac49f9f authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

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
parent 847735fd
Loading
Loading
Loading
Loading
+104 −82
Original line number Diff line number Diff line
@@ -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);
    }

    @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);
        }
        return new CursorAnchorInfoCompatWrapper(instance);
    }

    private static final class FakeHolder {
        static CursorAnchorInfoCompatWrapper sInstance = new CursorAnchorInfoCompatWrapper(null);
    }

    @UsedForTesting
    public static CursorAnchorInfoCompatWrapper getFake() {
        return FakeHolder.sInstance;
    private CursorAnchorInfoCompatWrapper() {
        // This class is not publicly instantiable.
    }

    @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;
        }
        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();
        }
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -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;
        }
+8 −14
Original line number Diff line number Diff line
@@ -791,22 +791,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    onExtractTextViewPreDraw();
                    return true;
                }
            };

    private void onExtractTextViewPreDraw() {
        // CursorAnchorInfo is available on L and later.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.L) {
            return;
                    // 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));
                        }
        if (!isFullscreenMode() || mExtractEditText == null) {
            return;
                    }
        final CursorAnchorInfo info = CursorAnchorInfoUtils.getCursorAnchorInfo(mExtractEditText);
        mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
                    return true;
                }
            };

    @Override
    public void setCandidatesView(final View view) {
@@ -1090,7 +1084,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
        if (isFullscreenMode()) {
            return;
        }
        mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
        mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.wrap(info));
    }

    /**
+29 −2
Original line number Diff line number Diff line
@@ -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;
        }