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