diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
index cf58d6a09bc1af6719a1941e0a59ca0b6e8fc8d4..f614b22576985fee99d91c44cd1051b936751f60 100644
--- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java
+++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
@@ -24,7 +24,6 @@ import android.os.Message;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
-import android.view.inputmethod.CursorAnchorInfo;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
@@ -154,13 +153,11 @@ public class TextDecorator {
      * {@code false} is the input method is finishing the full screen mode.
      */
     public void notifyFullScreenMode(final boolean fullScreenMode) {
-        final boolean currentFullScreenMode = mIsFullScreenMode;
-        if (!currentFullScreenMode && fullScreenMode) {
-            // Currently full screen mode is not supported.
-            // TODO: Support full screen mode.
-            mUiOperator.hideUi();
-        }
+        final boolean fullScreenModeChanged = (mIsFullScreenMode != fullScreenMode);
         mIsFullScreenMode = fullScreenMode;
+        if (fullScreenModeChanged) {
+            layoutLater();
+        }
     }
 
     /**
@@ -183,11 +180,6 @@ public class TextDecorator {
      * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
      */
     public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
-        if (mIsFullScreenMode) {
-            // TODO: Consider to call InputConnection#requestCursorAnchorInfo to disable the
-            // event callback to suppress unnecessary event callbacks.
-            return;
-        }
         mCursorAnchorInfoWrapper = info;
         // Do not use layoutLater() to minimize the latency.
         layoutImmediately();
@@ -240,11 +232,6 @@ public class TextDecorator {
     }
 
     private void layoutMain() {
-        if (mIsFullScreenMode) {
-            cancelLayoutInternalUnexpectedly("Full screen mode isn't yet supported.");
-            return;
-        }
-
         if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) {
             if (mMode == MODE_NONE) {
                 cancelLayoutInternalExpectedly("Not ready for layouting.");
@@ -328,7 +315,7 @@ public class TextDecorator {
                 return;
             }
         } else {
-            if (!TextUtils.isEmpty(composingText)) {
+            if (!mIsFullScreenMode && !TextUtils.isEmpty(composingText)) {
                 // This is an unexpected case.
                 // TODO: Document this.
                 mUiOperator.hideUi();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2fbf037f439543973fcead9bfd1d85a468e0249c..86fe6429b887cea2684aeebaf5856ff1d47bc6ac 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -46,6 +46,7 @@ import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
@@ -53,6 +54,7 @@ import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.annotations.UsedForTesting;
@@ -85,6 +87,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.CursorAnchorInfoUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.DialogUtils;
 import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
@@ -152,6 +155,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
     // TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
     private View mInputView;
     private SuggestionStripView mSuggestionStripView;
+    private TextView mExtractEditText;
 
     private RichInputMethodManager mRichImm;
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
@@ -757,6 +761,49 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         mInputLogic.setTextDecoratorUi(new TextDecoratorUi(this, view));
     }
 
+    @Override
+    public void setExtractView(final View view) {
+        final TextView prevExtractEditText = mExtractEditText;
+        super.setExtractView(view);
+        TextView nextExtractEditText = null;
+        if (view != null) {
+            final View extractEditText = view.findViewById(android.R.id.inputExtractEditText);
+            if (extractEditText instanceof TextView) {
+                nextExtractEditText = (TextView)extractEditText;
+            }
+        }
+        if (prevExtractEditText == nextExtractEditText) {
+            return;
+        }
+        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && prevExtractEditText != null) {
+            prevExtractEditText.getViewTreeObserver().removeOnPreDrawListener(
+                    mExtractTextViewPreDrawListener);
+        }
+        mExtractEditText = nextExtractEditText;
+        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && mExtractEditText != null) {
+            mExtractEditText.getViewTreeObserver().addOnPreDrawListener(
+                    mExtractTextViewPreDrawListener);
+        }
+    }
+
+    private final ViewTreeObserver.OnPreDrawListener mExtractTextViewPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    onExtractTextViewPreDraw();
+                    return true;
+                }
+            };
+
+    private void onExtractTextViewPreDraw() {
+        if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || !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.
@@ -1018,9 +1065,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
     // We cannot mark this method as @Override until new SDK becomes publicly available.
     // @Override
     public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
-        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
-            mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
+        if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || isFullscreenMode()) {
+            return;
         }
+        mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..9dc0524a262b0290d9d1dc37b899c704f840f18f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.inputmethodservice.ExtractEditText;
+import android.inputmethodservice.InputMethodService;
+import android.text.Layout;
+import android.text.Spannable;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.widget.TextView;
+
+/**
+ * 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 InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} event callback must be
+ * ignored because it reports the character locations of the target application rather than
+ * characters on {@link ExtractEditText}.
+ */
+public final class CursorAnchorInfoUtils {
+    private CursorAnchorInfoUtils() {
+        // This helper class is not instantiable.
+    }
+
+    private static boolean isPositionVisible(final View view, final float positionX,
+            final float positionY) {
+        final float[] position = new float[] { positionX, positionY };
+        View currentView = view;
+
+        while (currentView != null) {
+            if (currentView != view) {
+                // Local scroll is already taken into account in positionX/Y
+                position[0] -= currentView.getScrollX();
+                position[1] -= currentView.getScrollY();
+            }
+
+            if (position[0] < 0 || position[1] < 0 ||
+                    position[0] > currentView.getWidth() || position[1] > currentView.getHeight()) {
+                return false;
+            }
+
+            if (!currentView.getMatrix().isIdentity()) {
+                currentView.getMatrix().mapPoints(position);
+            }
+
+            position[0] += currentView.getLeft();
+            position[1] += currentView.getTop();
+
+            final ViewParent parent = currentView.getParent();
+            if (parent instanceof View) {
+                currentView = (View) parent;
+            } else {
+                // We've reached the ViewRoot, stop iterating
+                currentView = null;
+            }
+        }
+
+        // We've been able to walk up the view hierarchy and the position was never clipped
+        return true;
+    }
+
+    /**
+     * 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();
+        if (layout == null) {
+            return null;
+        }
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+
+        final int selectionStart = textView.getSelectionStart();
+        builder.setSelectionRange(selectionStart, textView.getSelectionEnd());
+
+        // Construct transformation matrix from view local coordinates to screen coordinates.
+        final Matrix viewToScreenMatrix = new Matrix(textView.getMatrix());
+        final int[] viewOriginInScreen = new int[2];
+        textView.getLocationOnScreen(viewOriginInScreen);
+        viewToScreenMatrix.postTranslate(viewOriginInScreen[0], viewOriginInScreen[1]);
+        builder.setMatrix(viewToScreenMatrix);
+
+        if (layout.getLineCount() == 0) {
+            return null;
+        }
+        final Rect lineBoundsWithoutOffset = new Rect();
+        final Rect lineBoundsWithOffset = new Rect();
+        layout.getLineBounds(0, lineBoundsWithoutOffset);
+        textView.getLineBounds(0, lineBoundsWithOffset);
+        final float viewportToContentHorizontalOffset = lineBoundsWithOffset.left
+                - lineBoundsWithoutOffset.left - textView.getScrollX();
+        final float viewportToContentVerticalOffset = lineBoundsWithOffset.top
+                - lineBoundsWithoutOffset.top - textView.getScrollY();
+
+        final CharSequence text = textView.getText();
+        if (text instanceof Spannable) {
+            // Here we assume that the composing text is marked as SPAN_COMPOSING flag. This is not
+            // necessarily true, but basically works.
+            int composingTextStart = text.length();
+            int composingTextEnd = 0;
+            final Spannable spannable = (Spannable) text;
+            final Object[] spans = spannable.getSpans(0, text.length(), Object.class);
+            for (Object span : spans) {
+                final int spanFlag = spannable.getSpanFlags(span);
+                if ((spanFlag & Spannable.SPAN_COMPOSING) != 0) {
+                    composingTextStart = Math.min(composingTextStart,
+                            spannable.getSpanStart(span));
+                    composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span));
+                }
+            }
+
+            final boolean hasComposingText =
+                    (0 <= composingTextStart) && (composingTextStart < composingTextEnd);
+            if (hasComposingText) {
+                final CharSequence composingText = text.subSequence(composingTextStart,
+                        composingTextEnd);
+                builder.setComposingText(composingTextStart, composingText);
+
+                final int minLine = layout.getLineForOffset(composingTextStart);
+                final int maxLine = layout.getLineForOffset(composingTextEnd - 1);
+                for (int line = minLine; line <= maxLine; ++line) {
+                    final int lineStart = layout.getLineStart(line);
+                    final int lineEnd = layout.getLineEnd(line);
+                    final int offsetStart = Math.max(lineStart, composingTextStart);
+                    final int offsetEnd = Math.min(lineEnd, composingTextEnd);
+                    final boolean ltrLine =
+                            layout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
+                    final float[] widths = new float[offsetEnd - offsetStart];
+                    layout.getPaint().getTextWidths(text, offsetStart, offsetEnd, widths);
+                    final float top = layout.getLineTop(line);
+                    final float bottom = layout.getLineBottom(line);
+                    for (int offset = offsetStart; offset < offsetEnd; ++offset) {
+                        final float charWidth = widths[offset - offsetStart];
+                        final boolean isRtl = layout.isRtlCharAt(offset);
+                        final float primary = layout.getPrimaryHorizontal(offset);
+                        final float secondary = layout.getSecondaryHorizontal(offset);
+                        // TODO: This doesn't work perfectly for text with custom styles and TAB
+                        // chars.
+                        final float left;
+                        final float right;
+                        if (ltrLine) {
+                            if (isRtl) {
+                                left = secondary - charWidth;
+                                right = secondary;
+                            } else {
+                                left = primary;
+                                right = primary + charWidth;
+                            }
+                        } else {
+                            if (!isRtl) {
+                                left = secondary;
+                                right = secondary + charWidth;
+                            } else {
+                                left = primary - charWidth;
+                                right = primary;
+                            }
+                        }
+                        // TODO: Check top-right and bottom-left as well.
+                        final float localLeft = left + viewportToContentHorizontalOffset;
+                        final float localRight = right + viewportToContentHorizontalOffset;
+                        final float localTop = top + viewportToContentVerticalOffset;
+                        final float localBottom = bottom + viewportToContentVerticalOffset;
+                        final boolean isTopLeftVisible = isPositionVisible(textView,
+                                localLeft, localTop);
+                        final boolean isBottomRightVisible =
+                                isPositionVisible(textView, localRight, localBottom);
+                        int characterBoundsFlags = 0;
+                        if (isTopLeftVisible || isBottomRightVisible) {
+                            characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+                        }
+                        if (!isTopLeftVisible || !isTopLeftVisible) {
+                            characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+                        }
+                        if (isRtl) {
+                            characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+                        }
+                        // Here offset is the index in Java chars.
+                        builder.addCharacterBounds(offset, localLeft, localTop, localRight,
+                                localBottom, characterBoundsFlags);
+                    }
+                }
+            }
+        }
+
+        // Treat selectionStart as the insertion point.
+        if (0 <= selectionStart) {
+            final int offset = selectionStart;
+            final int line = layout.getLineForOffset(offset);
+            final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
+                    + viewportToContentHorizontalOffset;
+            final float insertionMarkerTop = layout.getLineTop(line)
+                    + viewportToContentVerticalOffset;
+            final float insertionMarkerBaseline = layout.getLineBaseline(line)
+                    + viewportToContentVerticalOffset;
+            final float insertionMarkerBottom = layout.getLineBottom(line)
+                    + viewportToContentVerticalOffset;
+            final boolean isTopVisible =
+                    isPositionVisible(textView, insertionMarkerX, insertionMarkerTop);
+            final boolean isBottomVisible =
+                    isPositionVisible(textView, insertionMarkerX, insertionMarkerBottom);
+            int insertionMarkerFlags = 0;
+            if (isTopVisible || isBottomVisible) {
+                insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+            }
+            if (!isTopVisible || !isBottomVisible) {
+                insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+            }
+            if (layout.isRtlCharAt(offset)) {
+                insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+            }
+            builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
+                    insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags);
+        }
+        return builder.build();
+    }
+}