1cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa/*
2cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * Copyright (C) 2014 The Android Open Source Project
3cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa *
4cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * Licensed under the Apache License, Version 2.0 (the "License");
5cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * you may not use this file except in compliance with the License.
6cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * You may obtain a copy of the License at
7cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa *
8cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa *      http://www.apache.org/licenses/LICENSE-2.0
9cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa *
10cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * Unless required by applicable law or agreed to in writing, software
11cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * distributed under the License is distributed on an "AS IS" BASIS,
12cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * See the License for the specific language governing permissions and
14cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * limitations under the License.
15cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa */
16cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
17cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawapackage com.android.inputmethod.latin.utils;
18cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
19dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport android.annotation.TargetApi;
20cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.graphics.Matrix;
21cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.graphics.Rect;
22cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.inputmethodservice.ExtractEditText;
23cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.inputmethodservice.InputMethodService;
24dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport android.os.Build;
25cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.text.Layout;
26cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.text.Spannable;
275f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaokaimport android.text.Spanned;
28cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.view.View;
29cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.view.ViewParent;
30cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.view.inputmethod.CursorAnchorInfo;
31cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawaimport android.widget.TextView;
32cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
33dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport com.android.inputmethod.compat.BuildCompatUtils;
34dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
35dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa
36dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport javax.annotation.Nonnull;
37dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawaimport javax.annotation.Nullable;
38dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa
39cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa/**
40cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * This class allows input methods to extract {@link CursorAnchorInfo} directly from the given
41cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * {@link TextView}. This is useful and even necessary to support full-screen mode where the default
42cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} event callback must be
43cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * ignored because it reports the character locations of the target application rather than
44cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa * characters on {@link ExtractEditText}.
45cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa */
46cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawapublic final class CursorAnchorInfoUtils {
47cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    private CursorAnchorInfoUtils() {
48cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        // This helper class is not instantiable.
49cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    }
50cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
51cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    private static boolean isPositionVisible(final View view, final float positionX,
52cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final float positionY) {
53cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final float[] position = new float[] { positionX, positionY };
54cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        View currentView = view;
55cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
56cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        while (currentView != null) {
57cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (currentView != view) {
58cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                // Local scroll is already taken into account in positionX/Y
59cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                position[0] -= currentView.getScrollX();
60cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                position[1] -= currentView.getScrollY();
61cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
62cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
63cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (position[0] < 0 || position[1] < 0 ||
64cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    position[0] > currentView.getWidth() || position[1] > currentView.getHeight()) {
65cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                return false;
66cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
67cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
68cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (!currentView.getMatrix().isIdentity()) {
69cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                currentView.getMatrix().mapPoints(position);
70cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
71cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
72cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            position[0] += currentView.getLeft();
73cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            position[1] += currentView.getTop();
74cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
75cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final ViewParent parent = currentView.getParent();
76cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (parent instanceof View) {
77cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                currentView = (View) parent;
78cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            } else {
79cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                // We've reached the ViewRoot, stop iterating
80cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                currentView = null;
81cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
82cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        }
83cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
84cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        // We've been able to walk up the view hierarchy and the position was never clipped
85cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        return true;
86cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    }
87cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
88cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    /**
89dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}.
90dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to
91dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * be extracted.
92dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout.
93dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not
94dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     * ready to provide layout information.
95dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa     */
96dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    @Nullable
97dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    public static CursorAnchorInfoCompatWrapper extractFromTextView(
98dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa            @Nonnull final TextView textView) {
9966d30a4b226556f2f58b3701c6bc1974384486dbYohei Yukawa        if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
100dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa            return null;
101dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa        }
102dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa        return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView));
103dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    }
104dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa
105dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    /**
106cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa     * Returns {@link CursorAnchorInfo} from the given {@link TextView}.
107cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa     * @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted.
108cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa     * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it
109cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa     * is not feasible.
110cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa     */
11166d30a4b226556f2f58b3701c6bc1974384486dbYohei Yukawa    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
112dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    @Nullable
113dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa    private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) {
114dac49f9f6de83d9a0cb4d9e290340cde5297e0b3Yohei Yukawa        final Layout layout = textView.getLayout();
115cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        if (layout == null) {
116cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            return null;
117cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        }
118cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
119cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
120cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
121cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final int selectionStart = textView.getSelectionStart();
122cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        builder.setSelectionRange(selectionStart, textView.getSelectionEnd());
123cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
124cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        // Construct transformation matrix from view local coordinates to screen coordinates.
125cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final Matrix viewToScreenMatrix = new Matrix(textView.getMatrix());
126cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final int[] viewOriginInScreen = new int[2];
127cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        textView.getLocationOnScreen(viewOriginInScreen);
128cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        viewToScreenMatrix.postTranslate(viewOriginInScreen[0], viewOriginInScreen[1]);
129cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        builder.setMatrix(viewToScreenMatrix);
130cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
131cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        if (layout.getLineCount() == 0) {
132cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            return null;
133cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        }
134cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final Rect lineBoundsWithoutOffset = new Rect();
135cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final Rect lineBoundsWithOffset = new Rect();
136cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        layout.getLineBounds(0, lineBoundsWithoutOffset);
137cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        textView.getLineBounds(0, lineBoundsWithOffset);
138cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final float viewportToContentHorizontalOffset = lineBoundsWithOffset.left
139cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                - lineBoundsWithoutOffset.left - textView.getScrollX();
140cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final float viewportToContentVerticalOffset = lineBoundsWithOffset.top
141cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                - lineBoundsWithoutOffset.top - textView.getScrollY();
142cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
143cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        final CharSequence text = textView.getText();
144cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        if (text instanceof Spannable) {
145cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            // Here we assume that the composing text is marked as SPAN_COMPOSING flag. This is not
146cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            // necessarily true, but basically works.
147cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            int composingTextStart = text.length();
148cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            int composingTextEnd = 0;
149cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final Spannable spannable = (Spannable) text;
150cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final Object[] spans = spannable.getSpans(0, text.length(), Object.class);
151cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            for (Object span : spans) {
152cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                final int spanFlag = spannable.getSpanFlags(span);
1535f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka                if ((spanFlag & Spanned.SPAN_COMPOSING) != 0) {
154cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    composingTextStart = Math.min(composingTextStart,
155cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            spannable.getSpanStart(span));
156cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span));
157cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                }
158cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
159cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
160cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final boolean hasComposingText =
161cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    (0 <= composingTextStart) && (composingTextStart < composingTextEnd);
162cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (hasComposingText) {
163cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                final CharSequence composingText = text.subSequence(composingTextStart,
164cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        composingTextEnd);
165cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                builder.setComposingText(composingTextStart, composingText);
166cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
167cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                final int minLine = layout.getLineForOffset(composingTextStart);
168cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                final int maxLine = layout.getLineForOffset(composingTextEnd - 1);
169cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                for (int line = minLine; line <= maxLine; ++line) {
170cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final int lineStart = layout.getLineStart(line);
171cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final int lineEnd = layout.getLineEnd(line);
172cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final int offsetStart = Math.max(lineStart, composingTextStart);
173cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final int offsetEnd = Math.min(lineEnd, composingTextEnd);
174cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final boolean ltrLine =
175cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            layout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
176cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final float[] widths = new float[offsetEnd - offsetStart];
177cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    layout.getPaint().getTextWidths(text, offsetStart, offsetEnd, widths);
178cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final float top = layout.getLineTop(line);
179cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    final float bottom = layout.getLineBottom(line);
180cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    for (int offset = offsetStart; offset < offsetEnd; ++offset) {
181cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float charWidth = widths[offset - offsetStart];
182cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final boolean isRtl = layout.isRtlCharAt(offset);
183cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float primary = layout.getPrimaryHorizontal(offset);
184cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float secondary = layout.getSecondaryHorizontal(offset);
185cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        // TODO: This doesn't work perfectly for text with custom styles and TAB
186cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        // chars.
187cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float left;
188cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float right;
189cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        if (ltrLine) {
190cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            if (isRtl) {
191cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                left = secondary - charWidth;
192cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                right = secondary;
193cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            } else {
194cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                left = primary;
195cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                right = primary + charWidth;
196cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            }
197cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        } else {
198cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            if (!isRtl) {
199cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                left = secondary;
200cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                right = secondary + charWidth;
201cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            } else {
202cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                left = primary - charWidth;
203cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                right = primary;
204cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            }
205cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        }
206cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        // TODO: Check top-right and bottom-left as well.
207cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float localLeft = left + viewportToContentHorizontalOffset;
208cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float localRight = right + viewportToContentHorizontalOffset;
209cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float localTop = top + viewportToContentVerticalOffset;
210cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final float localBottom = bottom + viewportToContentVerticalOffset;
211cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final boolean isTopLeftVisible = isPositionVisible(textView,
212cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                localLeft, localTop);
213cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        final boolean isBottomRightVisible =
214cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                isPositionVisible(textView, localRight, localBottom);
215cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        int characterBoundsFlags = 0;
216cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        if (isTopLeftVisible || isBottomRightVisible) {
217cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
218cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        }
219cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        if (!isTopLeftVisible || !isTopLeftVisible) {
220cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
221cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        }
222cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        if (isRtl) {
223cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                            characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
224cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        }
225cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        // Here offset is the index in Java chars.
226cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                        builder.addCharacterBounds(offset, localLeft, localTop, localRight,
227cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                                localBottom, characterBoundsFlags);
228cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    }
229cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                }
230cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
231cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        }
232cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa
233cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        // Treat selectionStart as the insertion point.
234cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        if (0 <= selectionStart) {
235cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final int offset = selectionStart;
236cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final int line = layout.getLineForOffset(offset);
237cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
238cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    + viewportToContentHorizontalOffset;
239cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final float insertionMarkerTop = layout.getLineTop(line)
240cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    + viewportToContentVerticalOffset;
241cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final float insertionMarkerBaseline = layout.getLineBaseline(line)
242cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    + viewportToContentVerticalOffset;
243cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final float insertionMarkerBottom = layout.getLineBottom(line)
244cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    + viewportToContentVerticalOffset;
245cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final boolean isTopVisible =
246cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    isPositionVisible(textView, insertionMarkerX, insertionMarkerTop);
247cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            final boolean isBottomVisible =
248cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    isPositionVisible(textView, insertionMarkerX, insertionMarkerBottom);
249cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            int insertionMarkerFlags = 0;
250cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (isTopVisible || isBottomVisible) {
251cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
252cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
253cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (!isTopVisible || !isBottomVisible) {
254cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
255cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
256cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            if (layout.isRtlCharAt(offset)) {
257cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
258cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            }
259cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa            builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
260cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa                    insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags);
261cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        }
262cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa        return builder.build();
263cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa    }
264cd11905022306c9b95f8781f0f8b23a3570f30e9Yohei Yukawa}
265