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