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