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