1d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam/*
2d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * Copyright (C) 2016 The Android Open Source Project
3d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
4d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * Licensed under the Apache License, Version 2.0 (the "License");
5d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * you may not use this file except in compliance with the License.
6d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * You may obtain a copy of the License at
7d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
8d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *      http://www.apache.org/licenses/LICENSE-2.0
9d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
10d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * Unless required by applicable law or agreed to in writing, software
11d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * distributed under the License is distributed on an "AS IS" BASIS,
12d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * See the License for the specific language governing permissions and
14d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * limitations under the License.
15d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam */
16d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
17d832154e333a3a45b5faecd518b664ddd297183cMaurice Lampackage com.android.setupwizardlib.util;
18d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
19d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.graphics.Rect;
20d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.os.Bundle;
21d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
22d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.support.v4.widget.ExploreByTouchHelper;
23d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.text.Layout;
24d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.text.Spanned;
25d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.text.style.ClickableSpan;
26d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.util.Log;
27d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.view.accessibility.AccessibilityEvent;
28d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport android.widget.TextView;
29d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
30d832154e333a3a45b5faecd518b664ddd297183cMaurice Lamimport java.util.List;
31d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
32d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam/**
33d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and
34d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * clicked by accessibility services.
35d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
36d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * <p />Sample usage:
37d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * <pre>
38d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * LinkAccessibilityHelper mAccessibilityHelper;
39d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
40d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * private void init() {
41d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *     mAccessibilityHelper = new LinkAccessibilityHelper(myTextView);
42d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *     ViewCompat.setAccessibilityDelegate(myTextView, mLinkHelper);
43d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * }
44d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
45d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * {@literal @}Override
46d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * protected boolean dispatchHoverEvent({@literal @}NonNull MotionEvent event) {
47d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *     if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
48d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *         return true;
49d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *     }
50d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *     return super.dispatchHoverEvent(event);
51d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * }
52d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * </pre>
53d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam *
54d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * @see com.android.setupwizardlib.view.RichTextView
55d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam * @see android.support.v4.widget.ExploreByTouchHelper
56d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam */
57d832154e333a3a45b5faecd518b664ddd297183cMaurice Lampublic class LinkAccessibilityHelper extends ExploreByTouchHelper {
58d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
59d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private static final String TAG = "LinkAccessibilityHelper";
60d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
61d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private final TextView mView;
62d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private final Rect mTempRect = new Rect();
63d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
64d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    public LinkAccessibilityHelper(TextView view) {
65d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        super(view);
66d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        mView = view;
67d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
68d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
69d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    @Override
70d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    protected int getVirtualViewAt(float x, float y) {
71d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        final CharSequence text = mView.getText();
72d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (text instanceof Spanned) {
73d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            final Spanned spannedText = (Spanned) text;
74d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            final int offset = getOffsetForPosition(mView, x, y);
75d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
76d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            if (linkSpans.length == 1) {
77d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                ClickableSpan linkSpan = linkSpans[0];
78d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                return spannedText.getSpanStart(linkSpan);
79d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            }
80d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
81d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return INVALID_ID;
82d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
83d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
84d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    @Override
85d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
86d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        final CharSequence text = mView.getText();
87d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (text instanceof Spanned) {
88d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            final Spanned spannedText = (Spanned) text;
89d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
90d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                    ClickableSpan.class);
91d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            for (ClickableSpan span : linkSpans) {
92d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                virtualViewIds.add(spannedText.getSpanStart(span));
93d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            }
94d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
95d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
96d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
97d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    @Override
98d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
99d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        final ClickableSpan span = getSpanForOffset(virtualViewId);
100d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (span != null) {
101d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            event.setContentDescription(getTextForSpan(span));
102d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        } else {
103d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
104d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            event.setContentDescription(mView.getText());
105d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
106d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
107d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
108d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    @Override
109d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    protected void onPopulateNodeForVirtualView(int virtualViewId,
110d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            AccessibilityNodeInfoCompat info) {
111d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        final ClickableSpan span = getSpanForOffset(virtualViewId);
112d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (span != null) {
113d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            info.setContentDescription(getTextForSpan(span));
114d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        } else {
115d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
116d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            info.setContentDescription(mView.getText());
117d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
118d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        info.setFocusable(true);
119d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        info.setClickable(true);
120d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        getBoundsForSpan(span, mTempRect);
121cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam        if (mTempRect.isEmpty()) {
122d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
123d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            mTempRect.set(0, 0, 1, 1);
124d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
125cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam        info.setBoundsInParent(mTempRect);
126d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
127d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
128d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
129d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    @Override
130d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
131d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Bundle arguments) {
132d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
133d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            ClickableSpan span = getSpanForOffset(virtualViewId);
134d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            if (span != null) {
135d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                span.onClick(mView);
136d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                return true;
137d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            } else {
138d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
139d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            }
140d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
141d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return false;
142d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
143d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
144d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private ClickableSpan getSpanForOffset(int offset) {
145d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        CharSequence text = mView.getText();
146d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (text instanceof Spanned) {
147d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Spanned spannedText = (Spanned) text;
148d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
149d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            if (spans.length == 1) {
150d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                return spans[0];
151d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            }
152d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
153d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return null;
154d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
155d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
156d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private CharSequence getTextForSpan(ClickableSpan span) {
157d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        CharSequence text = mView.getText();
158d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (text instanceof Spanned) {
159d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            Spanned spannedText = (Spanned) text;
160d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            return spannedText.subSequence(spannedText.getSpanStart(span),
161d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam                    spannedText.getSpanEnd(span));
162d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
163d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return text;
164d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
165d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
166d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
167d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    // section on the first line.
168d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
169d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        CharSequence text = mView.getText();
170d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        outRect.setEmpty();
171d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (text instanceof Spanned) {
172d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam            final Layout layout = mView.getLayout();
173cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam            if (layout != null) {
174cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                Spanned spannedText = (Spanned) text;
175cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final int spanStart = spannedText.getSpanStart(span);
176cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final int spanEnd = spannedText.getSpanEnd(span);
177cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final float xStart = layout.getPrimaryHorizontal(spanStart);
178cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final float xEnd = layout.getPrimaryHorizontal(spanEnd);
179cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final int lineStart = layout.getLineForOffset(spanStart);
180cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                final int lineEnd = layout.getLineForOffset(spanEnd);
181cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                layout.getLineBounds(lineStart, outRect);
182cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                if (lineEnd == lineStart) {
18304eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // If the span is on a single line, adjust both the left and right bounds
18404eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // so outrect is exactly bounding the span.
18504eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    outRect.left = (int) Math.min(xStart, xEnd);
18604eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    outRect.right = (int) Math.max(xStart, xEnd);
18704eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                } else {
18804eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // If the span wraps across multiple lines, only use the first line (as returned
18904eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // by layout.getLineBounds above), and adjust the "start" of outrect to where
19004eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // the span starts, leaving the "end" of outrect at the end of the line.
19104eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    // ("start" being left for LTR, and right for RTL)
19204eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
19304eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                        outRect.right = (int) xStart;
19404eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    } else {
19504eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                        outRect.left = (int) xStart;
19604eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                    }
19704eecdd42f0bc9751825f9f21131a59852256278Maurice Lam                }
198cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam
199cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                // Offset for padding
200cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam                outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
201cf90658b5c16018c9f3db7fd1d872025cff5d1dcMaurice Lam            }
202d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        }
203d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return outRect;
204d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
205d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
206d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    // Compat implementation of TextView#getOffsetForPosition().
207d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
208d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private static int getOffsetForPosition(TextView view, float x, float y) {
209d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        if (view.getLayout() == null) return -1;
210d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        final int line = getLineAtCoordinate(view, y);
211d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return getOffsetAtCoordinate(view, line, x);
212d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
213d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
214d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
215d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        x -= view.getTotalPaddingLeft();
216d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        // Clamp the position to inside of the view.
217d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        x = Math.max(0.0f, x);
218d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
219d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        x += view.getScrollX();
220d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return x;
221d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
222d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
223d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private static int getLineAtCoordinate(TextView view, float y) {
224d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        y -= view.getTotalPaddingTop();
225d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        // Clamp the position to inside of the view.
226d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        y = Math.max(0.0f, y);
227d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
228d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        y += view.getScrollY();
229d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return view.getLayout().getLineForVertical((int) y);
230d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
231d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam
232d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
233d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        x = convertToLocalHorizontalCoordinate(view, x);
234d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam        return view.getLayout().getOffsetForHorizontal(line, x);
235d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam    }
236d832154e333a3a45b5faecd518b664ddd297183cMaurice Lam}
237