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