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 Locale mLocale;
74
75        protected BreakIterator mImpl;
76
77        public static CharacterTextSegmentIterator getInstance(Locale locale) {
78            if (sInstance == null) {
79                sInstance = new CharacterTextSegmentIterator(locale);
80            }
81            return sInstance;
82        }
83
84        private CharacterTextSegmentIterator(Locale locale) {
85            mLocale = locale;
86            onLocaleChanged(locale);
87            ViewRootImpl.addConfigCallback(this);
88        }
89
90        @Override
91        public void initialize(String text) {
92            super.initialize(text);
93            mImpl.setText(text);
94        }
95
96        @Override
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
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
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
158        public void onLowMemory() {
159            /* ignore */
160        }
161
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
170        public static WordTextSegmentIterator getInstance(Locale locale) {
171            if (sInstance == null) {
172                sInstance = new WordTextSegmentIterator(locale);
173            }
174            return sInstance;
175        }
176
177        private WordTextSegmentIterator(Locale locale) {
178           super(locale);
179        }
180
181        @Override
182        protected void onLocaleChanged(Locale locale) {
183            mImpl = BreakIterator.getWordInstance(locale);
184        }
185
186        @Override
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
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
238        private boolean isStartBoundary(int index) {
239            return isLetterOrDigit(index)
240                && (index == 0 || !isLetterOrDigit(index - 1));
241        }
242
243        private boolean isEndBoundary(int index) {
244            return (index > 0 && isLetterOrDigit(index - 1))
245                && (index == mText.length() || !isLetterOrDigit(index));
246        }
247
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
260        public static ParagraphTextSegmentIterator getInstance() {
261            if (sInstance == null) {
262                sInstance = new ParagraphTextSegmentIterator();
263            }
264            return sInstance;
265        }
266
267        @Override
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
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
320        private boolean isStartBoundary(int index) {
321            return (mText.charAt(index) != '\n'
322                && (index == 0 || mText.charAt(index - 1) == '\n'));
323        }
324
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