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