1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.graphics.Rect;
20 import android.text.Layout;
21 import android.text.Spannable;
22 import android.view.AccessibilityIterators.AbstractTextSegmentIterator;
23 
24 /**
25  * This class contains the implementation of text segment iterators
26  * for accessibility support.
27  */
28 final class AccessibilityIterators {
29 
30     static class LineTextSegmentIterator extends AbstractTextSegmentIterator {
31         private static LineTextSegmentIterator sLineInstance;
32 
33         protected static final int DIRECTION_START = -1;
34         protected static final int DIRECTION_END = 1;
35 
36         protected Layout mLayout;
37 
getInstance()38         public static LineTextSegmentIterator getInstance() {
39             if (sLineInstance == null) {
40                 sLineInstance = new LineTextSegmentIterator();
41             }
42             return sLineInstance;
43         }
44 
initialize(Spannable text, Layout layout)45         public void initialize(Spannable text, Layout layout) {
46             mText = text.toString();
47             mLayout = layout;
48         }
49 
50         @Override
following(int offset)51         public int[] following(int offset) {
52             final int textLegth = mText.length();
53             if (textLegth <= 0) {
54                 return null;
55             }
56             if (offset >= mText.length()) {
57                 return null;
58             }
59             int nextLine;
60             if (offset < 0) {
61                 nextLine = mLayout.getLineForOffset(0);
62             } else {
63                 final int currentLine = mLayout.getLineForOffset(offset);
64                 if (getLineEdgeIndex(currentLine, DIRECTION_START) == offset) {
65                     nextLine = currentLine;
66                 } else {
67                     nextLine = currentLine + 1;
68                 }
69             }
70             if (nextLine >= mLayout.getLineCount()) {
71                 return null;
72             }
73             final int start = getLineEdgeIndex(nextLine, DIRECTION_START);
74             final int end = getLineEdgeIndex(nextLine, DIRECTION_END) + 1;
75             return getRange(start, end);
76         }
77 
78         @Override
preceding(int offset)79         public int[] preceding(int offset) {
80             final int textLegth = mText.length();
81             if (textLegth <= 0) {
82                 return null;
83             }
84             if (offset <= 0) {
85                 return null;
86             }
87             int previousLine;
88             if (offset > mText.length()) {
89                 previousLine = mLayout.getLineForOffset(mText.length());
90             } else {
91                 final int currentLine = mLayout.getLineForOffset(offset);
92                 if (getLineEdgeIndex(currentLine, DIRECTION_END) + 1 == offset) {
93                     previousLine = currentLine;
94                 } else {
95                     previousLine = currentLine - 1;
96                 }
97             }
98             if (previousLine < 0) {
99                 return null;
100             }
101             final int start = getLineEdgeIndex(previousLine, DIRECTION_START);
102             final int end = getLineEdgeIndex(previousLine, DIRECTION_END) + 1;
103             return getRange(start, end);
104         }
105 
getLineEdgeIndex(int lineNumber, int direction)106         protected int getLineEdgeIndex(int lineNumber, int direction) {
107             final int paragraphDirection = mLayout.getParagraphDirection(lineNumber);
108             if (direction * paragraphDirection < 0) {
109                 return mLayout.getLineStart(lineNumber);
110             } else {
111                 return mLayout.getLineEnd(lineNumber) - 1;
112             }
113         }
114     }
115 
116     static class PageTextSegmentIterator extends LineTextSegmentIterator {
117         private static PageTextSegmentIterator sPageInstance;
118 
119         private TextView mView;
120 
121         private final Rect mTempRect = new Rect();
122 
getInstance()123         public static PageTextSegmentIterator getInstance() {
124             if (sPageInstance == null) {
125                 sPageInstance = new PageTextSegmentIterator();
126             }
127             return sPageInstance;
128         }
129 
initialize(TextView view)130         public void initialize(TextView view) {
131             super.initialize((Spannable) view.getIterableTextForAccessibility(), view.getLayout());
132             mView = view;
133         }
134 
135         @Override
following(int offset)136         public int[] following(int offset) {
137             final int textLength = mText.length();
138             if (textLength <= 0) {
139                 return null;
140             }
141             if (offset >= mText.length()) {
142                 return null;
143             }
144             if (!mView.getGlobalVisibleRect(mTempRect)) {
145                 return null;
146             }
147 
148             final int start = Math.max(0, offset);
149 
150             final int currentLine = mLayout.getLineForOffset(start);
151             final int currentLineTop = mLayout.getLineTop(currentLine);
152             final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
153                     - mView.getTotalPaddingBottom();
154             final int nextPageStartY = currentLineTop + pageHeight;
155             final int lastLineTop = mLayout.getLineTop(mLayout.getLineCount() - 1);
156             final int currentPageEndLine = (nextPageStartY < lastLineTop)
157                     ? mLayout.getLineForVertical(nextPageStartY) - 1 : mLayout.getLineCount() - 1;
158 
159             final int end = getLineEdgeIndex(currentPageEndLine, DIRECTION_END) + 1;
160 
161             return getRange(start, end);
162         }
163 
164         @Override
preceding(int offset)165         public int[] preceding(int offset) {
166             final int textLength = mText.length();
167             if (textLength <= 0) {
168                 return null;
169             }
170             if (offset <= 0) {
171                 return null;
172             }
173             if (!mView.getGlobalVisibleRect(mTempRect)) {
174                 return null;
175             }
176 
177             final int end = Math.min(mText.length(), offset);
178 
179             final int currentLine = mLayout.getLineForOffset(end);
180             final int currentLineTop = mLayout.getLineTop(currentLine);
181             final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
182                     - mView.getTotalPaddingBottom();
183             final int previousPageEndY = currentLineTop - pageHeight;
184             int currentPageStartLine = (previousPageEndY > 0) ?
185                      mLayout.getLineForVertical(previousPageEndY) : 0;
186             // If we're at the end of text, we're at the end of the current line rather than the
187             // start of the next line, so we should move up one fewer lines than we would otherwise.
188             if (end == mText.length() && (currentPageStartLine < currentLine)) {
189                 currentPageStartLine += 1;
190             }
191 
192             final int start = getLineEdgeIndex(currentPageStartLine, DIRECTION_START);
193 
194             return getRange(start, end);
195         }
196     }
197 }
198