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