blob: 54cfc00cb5b3d866e9946e6a47f6391447b39d65 [file] [log] [blame]
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07001/*
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
17package android.view;
18
Mathew Inwoode5ad5982018-08-17 15:07:52 +010019import android.annotation.UnsupportedAppUsage;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070020import android.content.res.Configuration;
21
22import java.text.BreakIterator;
23import java.util.Locale;
24
25/**
26 * This class contains the implementation of text segment iterators
27 * for accessibility support.
28 *
29 * Note: Such iterators are needed in the view package since we want
30 * to be able to iterator over content description of any view.
31 *
32 * @hide
33 */
34public final class AccessibilityIterators {
35
36 /**
37 * @hide
38 */
39 public static interface TextSegmentIterator {
40 public int[] following(int current);
41 public int[] preceding(int current);
42 }
43
44 /**
45 * @hide
46 */
47 public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070048
Mathew Inwoode5ad5982018-08-17 15:07:52 +010049 @UnsupportedAppUsage
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070050 protected String mText;
51
52 private final int[] mSegment = new int[2];
53
54 public void initialize(String text) {
55 mText = text;
56 }
57
58 protected int[] getRange(int start, int end) {
59 if (start < 0 || end < 0 || start == end) {
60 return null;
61 }
62 mSegment[0] = start;
63 mSegment[1] = end;
64 return mSegment;
65 }
66 }
67
68 static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
Andrii Kulian44607962017-03-16 11:06:24 -070069 implements ViewRootImpl.ConfigChangedCallback {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070070 private static CharacterTextSegmentIterator sInstance;
71
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070072 private Locale mLocale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070073
74 protected BreakIterator mImpl;
75
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070076 public static CharacterTextSegmentIterator getInstance(Locale locale) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070077 if (sInstance == null) {
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070078 sInstance = new CharacterTextSegmentIterator(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070079 }
80 return sInstance;
81 }
82
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070083 private CharacterTextSegmentIterator(Locale locale) {
84 mLocale = locale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070085 onLocaleChanged(locale);
86 ViewRootImpl.addConfigCallback(this);
87 }
88
89 @Override
90 public void initialize(String text) {
91 super.initialize(text);
92 mImpl.setText(text);
93 }
94
95 @Override
96 public int[] following(int offset) {
97 final int textLegth = mText.length();
98 if (textLegth <= 0) {
99 return null;
100 }
101 if (offset >= textLegth) {
102 return null;
103 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700104 int start = offset;
105 if (start < 0) {
106 start = 0;
107 }
108 while (!mImpl.isBoundary(start)) {
109 start = mImpl.following(start);
110 if (start == BreakIterator.DONE) {
111 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700112 }
113 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700114 final int end = mImpl.following(start);
115 if (end == BreakIterator.DONE) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700116 return null;
117 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700118 return getRange(start, end);
119 }
120
121 @Override
122 public int[] preceding(int offset) {
123 final int textLegth = mText.length();
124 if (textLegth <= 0) {
125 return null;
126 }
127 if (offset <= 0) {
128 return null;
129 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700130 int end = offset;
131 if (end > textLegth) {
132 end = textLegth;
133 }
134 while (!mImpl.isBoundary(end)) {
135 end = mImpl.preceding(end);
136 if (end == BreakIterator.DONE) {
137 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700138 }
139 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700140 final int start = mImpl.preceding(end);
141 if (start == BreakIterator.DONE) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700142 return null;
143 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700144 return getRange(start, end);
145 }
146
147 @Override
Andrii Kulian44607962017-03-16 11:06:24 -0700148 public void onConfigurationChanged(Configuration globalConfig) {
149 final Locale locale = globalConfig.getLocales().get(0);
Manpreetb99c60c2018-12-17 16:28:59 +0530150 if (locale == null) {
151 return;
152 }
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700153 if (!mLocale.equals(locale)) {
154 mLocale = locale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700155 onLocaleChanged(locale);
156 }
157 }
158
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700159 protected void onLocaleChanged(Locale locale) {
160 mImpl = BreakIterator.getCharacterInstance(locale);
161 }
162 }
163
164 static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
165 private static WordTextSegmentIterator sInstance;
166
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700167 public static WordTextSegmentIterator getInstance(Locale locale) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700168 if (sInstance == null) {
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700169 sInstance = new WordTextSegmentIterator(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700170 }
171 return sInstance;
172 }
173
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700174 private WordTextSegmentIterator(Locale locale) {
175 super(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700176 }
177
178 @Override
179 protected void onLocaleChanged(Locale locale) {
180 mImpl = BreakIterator.getWordInstance(locale);
181 }
182
183 @Override
184 public int[] following(int offset) {
185 final int textLegth = mText.length();
186 if (textLegth <= 0) {
187 return null;
188 }
189 if (offset >= mText.length()) {
190 return null;
191 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700192 int start = offset;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700193 if (start < 0) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700194 start = 0;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700195 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700196 while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
197 start = mImpl.following(start);
198 if (start == BreakIterator.DONE) {
199 return null;
200 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700201 }
202 final int end = mImpl.following(start);
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700203 if (end == BreakIterator.DONE || !isEndBoundary(end)) {
204 return null;
205 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700206 return getRange(start, end);
207 }
208
209 @Override
210 public int[] preceding(int offset) {
211 final int textLegth = mText.length();
212 if (textLegth <= 0) {
213 return null;
214 }
215 if (offset <= 0) {
216 return null;
217 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700218 int end = offset;
219 if (end > textLegth) {
220 end = textLegth;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700221 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700222 while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
223 end = mImpl.preceding(end);
224 if (end == BreakIterator.DONE) {
225 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700226 }
227 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700228 final int start = mImpl.preceding(end);
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700229 if (start == BreakIterator.DONE || !isStartBoundary(start)) {
230 return null;
231 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700232 return getRange(start, end);
233 }
234
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700235 private boolean isStartBoundary(int index) {
236 return isLetterOrDigit(index)
237 && (index == 0 || !isLetterOrDigit(index - 1));
238 }
239
240 private boolean isEndBoundary(int index) {
241 return (index > 0 && isLetterOrDigit(index - 1))
242 && (index == mText.length() || !isLetterOrDigit(index));
243 }
244
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700245 private boolean isLetterOrDigit(int index) {
246 if (index >= 0 && index < mText.length()) {
247 final int codePoint = mText.codePointAt(index);
248 return Character.isLetterOrDigit(codePoint);
249 }
250 return false;
251 }
252 }
253
254 static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
255 private static ParagraphTextSegmentIterator sInstance;
256
257 public static ParagraphTextSegmentIterator getInstance() {
258 if (sInstance == null) {
259 sInstance = new ParagraphTextSegmentIterator();
260 }
261 return sInstance;
262 }
263
264 @Override
265 public int[] following(int offset) {
266 final int textLength = mText.length();
267 if (textLength <= 0) {
268 return null;
269 }
270 if (offset >= textLength) {
271 return null;
272 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700273 int start = offset;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700274 if (start < 0) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700275 start = 0;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700276 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700277 while (start < textLength && mText.charAt(start) == '\n'
278 && !isStartBoundary(start)) {
alanv97688322012-05-17 13:54:30 -0700279 start++;
280 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700281 if (start >= textLength) {
282 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700283 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700284 int end = start + 1;
285 while (end < textLength && !isEndBoundary(end)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700286 end++;
287 }
288 return getRange(start, end);
289 }
290
291 @Override
292 public int[] preceding(int offset) {
293 final int textLength = mText.length();
294 if (textLength <= 0) {
295 return null;
296 }
297 if (offset <= 0) {
298 return null;
299 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700300 int end = offset;
301 if (end > textLength) {
302 end = textLength;
303 }
304 while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
305 end--;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700306 }
307 if (end <= 0) {
308 return null;
309 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700310 int start = end - 1;
311 while (start > 0 && !isStartBoundary(start)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700312 start--;
313 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700314 return getRange(start, end);
315 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700316
317 private boolean isStartBoundary(int index) {
318 return (mText.charAt(index) != '\n'
319 && (index == 0 || mText.charAt(index - 1) == '\n'));
320 }
321
322 private boolean isEndBoundary(int index) {
323 return (index > 0 && mText.charAt(index - 1) != '\n'
324 && (index == mText.length() || mText.charAt(index) == '\n'));
325 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700326 }
327}