blob: bee04f4d8036f9dd94c34509e0d4d364dfe01fbe [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
Artur Satayevdf439592019-12-10 17:47:53 +000019import android.compat.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
Artur Satayev751e5512019-11-15 19:12:49 +000050 public AbstractTextSegmentIterator() {
51 }
52
53 @UnsupportedAppUsage
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070054 protected String mText;
55
56 private final int[] mSegment = new int[2];
57
58 public void initialize(String text) {
59 mText = text;
60 }
61
62 protected int[] getRange(int start, int end) {
63 if (start < 0 || end < 0 || start == end) {
64 return null;
65 }
66 mSegment[0] = start;
67 mSegment[1] = end;
68 return mSegment;
69 }
70 }
71
72 static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
Andrii Kulian44607962017-03-16 11:06:24 -070073 implements ViewRootImpl.ConfigChangedCallback {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070074 private static CharacterTextSegmentIterator sInstance;
75
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070076 private Locale mLocale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070077
78 protected BreakIterator mImpl;
79
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070080 public static CharacterTextSegmentIterator getInstance(Locale locale) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070081 if (sInstance == null) {
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070082 sInstance = new CharacterTextSegmentIterator(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070083 }
84 return sInstance;
85 }
86
Svetoslav Ganovbbd31552012-06-11 12:08:18 -070087 private CharacterTextSegmentIterator(Locale locale) {
88 mLocale = locale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070089 onLocaleChanged(locale);
90 ViewRootImpl.addConfigCallback(this);
91 }
92
93 @Override
94 public void initialize(String text) {
95 super.initialize(text);
96 mImpl.setText(text);
97 }
98
99 @Override
100 public int[] following(int offset) {
101 final int textLegth = mText.length();
102 if (textLegth <= 0) {
103 return null;
104 }
105 if (offset >= textLegth) {
106 return null;
107 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700108 int start = offset;
109 if (start < 0) {
110 start = 0;
111 }
112 while (!mImpl.isBoundary(start)) {
113 start = mImpl.following(start);
114 if (start == BreakIterator.DONE) {
115 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700116 }
117 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700118 final int end = mImpl.following(start);
119 if (end == BreakIterator.DONE) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700120 return null;
121 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700122 return getRange(start, end);
123 }
124
125 @Override
126 public int[] preceding(int offset) {
127 final int textLegth = mText.length();
128 if (textLegth <= 0) {
129 return null;
130 }
131 if (offset <= 0) {
132 return null;
133 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700134 int end = offset;
135 if (end > textLegth) {
136 end = textLegth;
137 }
138 while (!mImpl.isBoundary(end)) {
139 end = mImpl.preceding(end);
140 if (end == BreakIterator.DONE) {
141 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700142 }
143 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700144 final int start = mImpl.preceding(end);
145 if (start == BreakIterator.DONE) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700146 return null;
147 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700148 return getRange(start, end);
149 }
150
151 @Override
Andrii Kulian44607962017-03-16 11:06:24 -0700152 public void onConfigurationChanged(Configuration globalConfig) {
153 final Locale locale = globalConfig.getLocales().get(0);
Manpreetb99c60c2018-12-17 16:28:59 +0530154 if (locale == null) {
155 return;
156 }
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700157 if (!mLocale.equals(locale)) {
158 mLocale = locale;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700159 onLocaleChanged(locale);
160 }
161 }
162
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700163 protected void onLocaleChanged(Locale locale) {
164 mImpl = BreakIterator.getCharacterInstance(locale);
165 }
166 }
167
168 static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
169 private static WordTextSegmentIterator sInstance;
170
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700171 public static WordTextSegmentIterator getInstance(Locale locale) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700172 if (sInstance == null) {
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700173 sInstance = new WordTextSegmentIterator(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700174 }
175 return sInstance;
176 }
177
Svetoslav Ganovbbd31552012-06-11 12:08:18 -0700178 private WordTextSegmentIterator(Locale locale) {
179 super(locale);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700180 }
181
182 @Override
183 protected void onLocaleChanged(Locale locale) {
184 mImpl = BreakIterator.getWordInstance(locale);
185 }
186
187 @Override
188 public int[] following(int offset) {
189 final int textLegth = mText.length();
190 if (textLegth <= 0) {
191 return null;
192 }
193 if (offset >= mText.length()) {
194 return null;
195 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700196 int start = offset;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700197 if (start < 0) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700198 start = 0;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700199 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700200 while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
201 start = mImpl.following(start);
202 if (start == BreakIterator.DONE) {
203 return null;
204 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700205 }
206 final int end = mImpl.following(start);
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700207 if (end == BreakIterator.DONE || !isEndBoundary(end)) {
208 return null;
209 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700210 return getRange(start, end);
211 }
212
213 @Override
214 public int[] preceding(int offset) {
215 final int textLegth = mText.length();
216 if (textLegth <= 0) {
217 return null;
218 }
219 if (offset <= 0) {
220 return null;
221 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700222 int end = offset;
223 if (end > textLegth) {
224 end = textLegth;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700225 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700226 while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
227 end = mImpl.preceding(end);
228 if (end == BreakIterator.DONE) {
229 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700230 }
231 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700232 final int start = mImpl.preceding(end);
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700233 if (start == BreakIterator.DONE || !isStartBoundary(start)) {
234 return null;
235 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700236 return getRange(start, end);
237 }
238
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700239 private boolean isStartBoundary(int index) {
240 return isLetterOrDigit(index)
241 && (index == 0 || !isLetterOrDigit(index - 1));
242 }
243
244 private boolean isEndBoundary(int index) {
245 return (index > 0 && isLetterOrDigit(index - 1))
246 && (index == mText.length() || !isLetterOrDigit(index));
247 }
248
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700249 private boolean isLetterOrDigit(int index) {
250 if (index >= 0 && index < mText.length()) {
251 final int codePoint = mText.codePointAt(index);
252 return Character.isLetterOrDigit(codePoint);
253 }
254 return false;
255 }
256 }
257
258 static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
259 private static ParagraphTextSegmentIterator sInstance;
260
261 public static ParagraphTextSegmentIterator getInstance() {
262 if (sInstance == null) {
263 sInstance = new ParagraphTextSegmentIterator();
264 }
265 return sInstance;
266 }
267
268 @Override
269 public int[] following(int offset) {
270 final int textLength = mText.length();
271 if (textLength <= 0) {
272 return null;
273 }
274 if (offset >= textLength) {
275 return null;
276 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700277 int start = offset;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700278 if (start < 0) {
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700279 start = 0;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700280 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700281 while (start < textLength && mText.charAt(start) == '\n'
282 && !isStartBoundary(start)) {
alanv97688322012-05-17 13:54:30 -0700283 start++;
284 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700285 if (start >= textLength) {
286 return null;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700287 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700288 int end = start + 1;
289 while (end < textLength && !isEndBoundary(end)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700290 end++;
291 }
292 return getRange(start, end);
293 }
294
295 @Override
296 public int[] preceding(int offset) {
297 final int textLength = mText.length();
298 if (textLength <= 0) {
299 return null;
300 }
301 if (offset <= 0) {
302 return null;
303 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700304 int end = offset;
305 if (end > textLength) {
306 end = textLength;
307 }
308 while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
309 end--;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700310 }
311 if (end <= 0) {
312 return null;
313 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700314 int start = end - 1;
315 while (start > 0 && !isStartBoundary(start)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700316 start--;
317 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700318 return getRange(start, end);
319 }
Svetoslav Ganov39f2aee2012-05-29 09:15:30 -0700320
321 private boolean isStartBoundary(int index) {
322 return (mText.charAt(index) != '\n'
323 && (index == 0 || mText.charAt(index - 1) == '\n'));
324 }
325
326 private boolean isEndBoundary(int index) {
327 return (index > 0 && mText.charAt(index - 1) != '\n'
328 && (index == mText.length() || mText.charAt(index) == '\n'));
329 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700330 }
331}