diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
index 5af31795ca264934fb33c82e2b20586b9bae0934..f4f54b624957cbccd6639da302764b2c8a964f3e 100644
--- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java
@@ -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;
+    private CursorAnchorInfoCompatWrapper() {
+        // This class is not publicly instantiable.
     }
 
-    @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();
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
index c22717f956ede6295ffd1a1b8af7f8cf3be86c90..ddc65bf36286e8617bc03bf08b6c2028394d9999 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
@@ -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;
         }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 77477d2d306c6b7bb2e8e9926d8265bc46148e4e..632ed2aac49af057fbbb12dbcbd01712bd54593b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -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));
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
index 9dc0524a262b0290d9d1dc37b899c704f840f18f..e0561890150984d2138d70fe53e92a6b1d857417 100644
--- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
@@ -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;
         }