Skip to content

Commit 6d17a93

Browse files
committed
Text traversal at various granularities.
1. Implementing text content navigation at various granularities. For views that have content description but no text the content description is the traversed at character and word granularities. For views that inherit from TextView the supported granularities are character, word, line, and page. bug:5932640 Conflicts: core/java/android/view/View.java Conflicts: core/java/android/view/View.java Change-Id: I66d1e16ce9ac5d6b49f036b17c087b2a7075e4c0
1 parent 2551e5a commit 6d17a93

File tree

7 files changed

+861
-13
lines changed

7 files changed

+861
-13
lines changed

api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25090,6 +25090,7 @@ package android.view.accessibility {
2509025090
method public void appendRecord(android.view.accessibility.AccessibilityRecord);
2509125091
method public int describeContents();
2509225092
method public static java.lang.String eventTypeToString(int);
25093+
method public int getAction();
2509325094
method public long getEventTime();
2509425095
method public int getEventType();
2509525096
method public int getMovementGranularity();
@@ -25100,6 +25101,7 @@ package android.view.accessibility {
2510025101
method public static android.view.accessibility.AccessibilityEvent obtain(int);
2510125102
method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
2510225103
method public static android.view.accessibility.AccessibilityEvent obtain();
25104+
method public void setAction(int);
2510325105
method public void setEventTime(long);
2510425106
method public void setEventType(int);
2510525107
method public void setMovementGranularity(int);
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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 {
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+
}

0 commit comments

Comments
 (0)