• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.view;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.res.Configuration;
21 import android.os.Build;
22 
23 import java.text.BreakIterator;
24 import java.util.Locale;
25 
26 /**
27  * This class contains the implementation of text segment iterators
28  * for accessibility support.
29  *
30  * Note: Such iterators are needed in the view package since we want
31  * to be able to iterator over content description of any view.
32  *
33  * @hide
34  */
35 public final class AccessibilityIterators {
36 
37     /**
38      * @hide
39      */
40     public static interface TextSegmentIterator {
following(int current)41         public int[] following(int current);
preceding(int current)42         public int[] preceding(int current);
43     }
44 
45     /**
46      * @hide
47      */
48     public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
49 
50         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AbstractTextSegmentIterator()51         public AbstractTextSegmentIterator() {
52         }
53 
54         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
55         protected String mText;
56 
57         private final int[] mSegment = new int[2];
58 
initialize(String text)59         public void initialize(String text) {
60             mText = text;
61         }
62 
getRange(int start, int end)63         protected int[] getRange(int start, int end) {
64             if (start < 0 || end < 0 || start ==  end) {
65                 return null;
66             }
67             mSegment[0] = start;
68             mSegment[1] = end;
69             return mSegment;
70         }
71     }
72 
73     static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator
74             implements ViewRootImpl.ConfigChangedCallback {
75         private static CharacterTextSegmentIterator sInstance;
76 
77         private Locale mLocale;
78 
79         protected BreakIterator mImpl;
80 
getInstance(Locale locale)81         public static CharacterTextSegmentIterator getInstance(Locale locale) {
82             if (sInstance == null) {
83                 sInstance = new CharacterTextSegmentIterator(locale);
84             }
85             return sInstance;
86         }
87 
CharacterTextSegmentIterator(Locale locale)88         private CharacterTextSegmentIterator(Locale locale) {
89             mLocale = locale;
90             onLocaleChanged(locale);
91             ViewRootImpl.addConfigCallback(this);
92         }
93 
94         @Override
initialize(String text)95         public void initialize(String text) {
96             super.initialize(text);
97             mImpl.setText(text);
98         }
99 
100         @Override
following(int offset)101         public int[] following(int offset) {
102             final int textLegth = mText.length();
103             if (textLegth <= 0) {
104                 return null;
105             }
106             if (offset >= textLegth) {
107                 return null;
108             }
109             int start = offset;
110             if (start < 0) {
111                 start = 0;
112             }
113             while (!mImpl.isBoundary(start)) {
114                 start = mImpl.following(start);
115                 if (start == BreakIterator.DONE) {
116                     return null;
117                 }
118             }
119             final int end = mImpl.following(start);
120             if (end == BreakIterator.DONE) {
121                 return null;
122             }
123             return getRange(start, end);
124         }
125 
126         @Override
preceding(int offset)127         public int[] preceding(int offset) {
128             final int textLegth = mText.length();
129             if (textLegth <= 0) {
130                 return null;
131             }
132             if (offset <= 0) {
133                 return null;
134             }
135             int end = offset;
136             if (end > textLegth) {
137                 end = textLegth;
138             }
139             while (!mImpl.isBoundary(end)) {
140                 end = mImpl.preceding(end);
141                 if (end == BreakIterator.DONE) {
142                     return null;
143                 }
144             }
145             final int start = mImpl.preceding(end);
146             if (start == BreakIterator.DONE) {
147                 return null;
148             }
149             return getRange(start, end);
150         }
151 
152         @Override
onConfigurationChanged(Configuration globalConfig)153         public void onConfigurationChanged(Configuration globalConfig) {
154             final Locale locale = globalConfig.getLocales().get(0);
155             if (locale == null) {
156                 return;
157             }
158             if (!mLocale.equals(locale)) {
159                 mLocale = locale;
160                 onLocaleChanged(locale);
161             }
162         }
163 
onLocaleChanged(Locale locale)164         protected void onLocaleChanged(Locale locale) {
165             mImpl = BreakIterator.getCharacterInstance(locale);
166         }
167     }
168 
169     static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
170         private static WordTextSegmentIterator sInstance;
171 
getInstance(Locale locale)172         public static WordTextSegmentIterator getInstance(Locale locale) {
173             if (sInstance == null) {
174                 sInstance = new WordTextSegmentIterator(locale);
175             }
176             return sInstance;
177         }
178 
WordTextSegmentIterator(Locale locale)179         private WordTextSegmentIterator(Locale locale) {
180            super(locale);
181         }
182 
183         @Override
onLocaleChanged(Locale locale)184         protected void onLocaleChanged(Locale locale) {
185             mImpl = BreakIterator.getWordInstance(locale);
186         }
187 
188         @Override
following(int offset)189         public int[] following(int offset) {
190             final int textLegth = mText.length();
191             if (textLegth <= 0) {
192                 return null;
193             }
194             if (offset >= mText.length()) {
195                 return null;
196             }
197             int start = offset;
198             if (start < 0) {
199                 start = 0;
200             }
201             while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
202                 start = mImpl.following(start);
203                 if (start == BreakIterator.DONE) {
204                     return null;
205                 }
206             }
207             final int end = mImpl.following(start);
208             if (end == BreakIterator.DONE || !isEndBoundary(end)) {
209                 return null;
210             }
211             return getRange(start, end);
212         }
213 
214         @Override
preceding(int offset)215         public int[] preceding(int offset) {
216             final int textLegth = mText.length();
217             if (textLegth <= 0) {
218                 return null;
219             }
220             if (offset <= 0) {
221                 return null;
222             }
223             int end = offset;
224             if (end > textLegth) {
225                 end = textLegth;
226             }
227             while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
228                 end = mImpl.preceding(end);
229                 if (end == BreakIterator.DONE) {
230                     return null;
231                 }
232             }
233             final int start = mImpl.preceding(end);
234             if (start == BreakIterator.DONE || !isStartBoundary(start)) {
235                 return null;
236             }
237             return getRange(start, end);
238         }
239 
isStartBoundary(int index)240         private boolean isStartBoundary(int index) {
241             return isLetterOrDigit(index)
242                 && (index == 0 || !isLetterOrDigit(index - 1));
243         }
244 
isEndBoundary(int index)245         private boolean isEndBoundary(int index) {
246             return (index > 0 && isLetterOrDigit(index - 1))
247                 && (index == mText.length() || !isLetterOrDigit(index));
248         }
249 
isLetterOrDigit(int index)250         private boolean isLetterOrDigit(int index) {
251             if (index >= 0 && index < mText.length()) {
252                 final int codePoint = mText.codePointAt(index);
253                 return Character.isLetterOrDigit(codePoint);
254             }
255             return false;
256         }
257     }
258 
259     static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator {
260         private static ParagraphTextSegmentIterator sInstance;
261 
getInstance()262         public static ParagraphTextSegmentIterator getInstance() {
263             if (sInstance == null) {
264                 sInstance = new ParagraphTextSegmentIterator();
265             }
266             return sInstance;
267         }
268 
269         @Override
following(int offset)270         public int[] following(int offset) {
271             final int textLength = mText.length();
272             if (textLength <= 0) {
273                 return null;
274             }
275             if (offset >= textLength) {
276                 return null;
277             }
278             int start = offset;
279             if (start < 0) {
280                 start = 0;
281             }
282             while (start < textLength && mText.charAt(start) == '\n'
283                     && !isStartBoundary(start)) {
284                 start++;
285             }
286             if (start >= textLength) {
287                 return null;
288             }
289             int end = start + 1;
290             while (end < textLength && !isEndBoundary(end)) {
291                 end++;
292             }
293             return getRange(start, end);
294         }
295 
296         @Override
preceding(int offset)297         public int[] preceding(int offset) {
298             final int textLength = mText.length();
299             if (textLength <= 0) {
300                 return null;
301             }
302             if (offset <= 0) {
303                 return null;
304             }
305             int end = offset;
306             if (end > textLength) {
307                 end = textLength;
308             }
309             while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
310                 end--;
311             }
312             if (end <= 0) {
313                 return null;
314             }
315             int start = end - 1;
316             while (start > 0 && !isStartBoundary(start)) {
317                 start--;
318             }
319             return getRange(start, end);
320         }
321 
isStartBoundary(int index)322         private boolean isStartBoundary(int index) {
323             return (mText.charAt(index) != '\n'
324                 && (index == 0 || mText.charAt(index - 1) == '\n'));
325         }
326 
isEndBoundary(int index)327         private boolean isEndBoundary(int index) {
328             return (index > 0 && mText.charAt(index - 1) != '\n'
329                 && (index == mText.length() || mText.charAt(index) == '\n'));
330         }
331     }
332 }
333