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.annotation.UnsupportedAppUsage;
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         @UnsupportedAppUsage
50         protected String mText;
51 
52         private final int[] mSegment = new int[2];
53 
initialize(String text)54         public void initialize(String text) {
55             mText = text;
56         }
57 
getRange(int start, int end)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
69             implements ViewRootImpl.ConfigChangedCallback {
70         private static CharacterTextSegmentIterator sInstance;
71 
72         private Locale mLocale;
73 
74         protected BreakIterator mImpl;
75 
getInstance(Locale locale)76         public static CharacterTextSegmentIterator getInstance(Locale locale) {
77             if (sInstance == null) {
78                 sInstance = new CharacterTextSegmentIterator(locale);
79             }
80             return sInstance;
81         }
82 
CharacterTextSegmentIterator(Locale locale)83         private CharacterTextSegmentIterator(Locale locale) {
84             mLocale = locale;
85             onLocaleChanged(locale);
86             ViewRootImpl.addConfigCallback(this);
87         }
88 
89         @Override
initialize(String text)90         public void initialize(String text) {
91             super.initialize(text);
92             mImpl.setText(text);
93         }
94 
95         @Override
following(int offset)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             }
104             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;
112                 }
113             }
114             final int end = mImpl.following(start);
115             if (end == BreakIterator.DONE) {
116                 return null;
117             }
118             return getRange(start, end);
119         }
120 
121         @Override
preceding(int offset)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             }
130             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;
138                 }
139             }
140             final int start = mImpl.preceding(end);
141             if (start == BreakIterator.DONE) {
142                 return null;
143             }
144             return getRange(start, end);
145         }
146 
147         @Override
onConfigurationChanged(Configuration globalConfig)148         public void onConfigurationChanged(Configuration globalConfig) {
149             final Locale locale = globalConfig.getLocales().get(0);
150             if (locale == null) {
151                 return;
152             }
153             if (!mLocale.equals(locale)) {
154                 mLocale = locale;
155                 onLocaleChanged(locale);
156             }
157         }
158 
onLocaleChanged(Locale locale)159         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 
getInstance(Locale locale)167         public static WordTextSegmentIterator getInstance(Locale locale) {
168             if (sInstance == null) {
169                 sInstance = new WordTextSegmentIterator(locale);
170             }
171             return sInstance;
172         }
173 
WordTextSegmentIterator(Locale locale)174         private WordTextSegmentIterator(Locale locale) {
175            super(locale);
176         }
177 
178         @Override
onLocaleChanged(Locale locale)179         protected void onLocaleChanged(Locale locale) {
180             mImpl = BreakIterator.getWordInstance(locale);
181         }
182 
183         @Override
following(int offset)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             }
192             int start = offset;
193             if (start < 0) {
194                 start = 0;
195             }
196             while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
197                 start = mImpl.following(start);
198                 if (start == BreakIterator.DONE) {
199                     return null;
200                 }
201             }
202             final int end = mImpl.following(start);
203             if (end == BreakIterator.DONE || !isEndBoundary(end)) {
204                 return null;
205             }
206             return getRange(start, end);
207         }
208 
209         @Override
preceding(int offset)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             }
218             int end = offset;
219             if (end > textLegth) {
220                 end = textLegth;
221             }
222             while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
223                 end = mImpl.preceding(end);
224                 if (end == BreakIterator.DONE) {
225                     return null;
226                 }
227             }
228             final int start = mImpl.preceding(end);
229             if (start == BreakIterator.DONE || !isStartBoundary(start)) {
230                 return null;
231             }
232             return getRange(start, end);
233         }
234 
isStartBoundary(int index)235         private boolean isStartBoundary(int index) {
236             return isLetterOrDigit(index)
237                 && (index == 0 || !isLetterOrDigit(index - 1));
238         }
239 
isEndBoundary(int index)240         private boolean isEndBoundary(int index) {
241             return (index > 0 && isLetterOrDigit(index - 1))
242                 && (index == mText.length() || !isLetterOrDigit(index));
243         }
244 
isLetterOrDigit(int index)245         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 
getInstance()257         public static ParagraphTextSegmentIterator getInstance() {
258             if (sInstance == null) {
259                 sInstance = new ParagraphTextSegmentIterator();
260             }
261             return sInstance;
262         }
263 
264         @Override
following(int offset)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             }
273             int start = offset;
274             if (start < 0) {
275                 start = 0;
276             }
277             while (start < textLength && mText.charAt(start) == '\n'
278                     && !isStartBoundary(start)) {
279                 start++;
280             }
281             if (start >= textLength) {
282                 return null;
283             }
284             int end = start + 1;
285             while (end < textLength && !isEndBoundary(end)) {
286                 end++;
287             }
288             return getRange(start, end);
289         }
290 
291         @Override
preceding(int offset)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             }
300             int end = offset;
301             if (end > textLength) {
302                 end = textLength;
303             }
304             while(end > 0 && mText.charAt(end - 1) == '\n' && !isEndBoundary(end)) {
305                 end--;
306             }
307             if (end <= 0) {
308                 return null;
309             }
310             int start = end - 1;
311             while (start > 0 && !isStartBoundary(start)) {
312                 start--;
313             }
314             return getRange(start, end);
315         }
316 
isStartBoundary(int index)317         private boolean isStartBoundary(int index) {
318             return (mText.charAt(index) != '\n'
319                 && (index == 0 || mText.charAt(index - 1) == '\n'));
320         }
321 
isEndBoundary(int index)322         private boolean isEndBoundary(int index) {
323             return (index > 0 && mText.charAt(index - 1) != '\n'
324                 && (index == mText.length() || mText.charAt(index) == '\n'));
325         }
326     }
327 }
328