Skip to content

Commit 39f2aee

Browse files
committed
Updating the behaviour of accessibility text iterators.
1. Iterators were skipping content on reversing direction. 2. The cursor was positioned at the beginning of the next text segment when moving forward and at end of the previous text segment when moving backwards. This is incorrect and now the cursor is positioned at the end of the segment when moving forward and at the beginning when moving backward. 3. The cursor position was not properly set when reaching the end/start of the text. 4. The iterators were reporting strictly the next/previous segment even if the cursor is within such a segment. Thus, when traversing some content may be skipped. Now moving forward moves the selection to the next segment end and the start position is either the old index if it was within a segment or the start of the segment. Same in reverse. bug:6575099 Change-Id: Ib48a649cec53910339baf831a75e26440be6e576
1 parent a587b89 commit 39f2aee

File tree

4 files changed

+128
-158
lines changed

4 files changed

+128
-158
lines changed

core/java/android/view/AccessibilityIterators.java

Lines changed: 74 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public static interface TextSegmentIterator {
4747
* @hide
4848
*/
4949
public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator {
50-
protected static final int DONE = -1;
5150

5251
protected String mText;
5352

@@ -104,20 +103,20 @@ public int[] following(int offset) {
104103
if (offset >= textLegth) {
105104
return null;
106105
}
107-
int start = -1;
108-
if (offset < 0) {
109-
offset = 0;
110-
if (mImpl.isBoundary(offset)) {
111-
start = offset;
112-
}
113-
}
106+
int start = offset;
114107
if (start < 0) {
115-
start = mImpl.following(offset);
108+
start = 0;
116109
}
117-
if (start < 0) {
118-
return null;
110+
while (!mImpl.isBoundary(start)) {
111+
start = mImpl.following(start);
112+
if (start == BreakIterator.DONE) {
113+
return null;
114+
}
119115
}
120116
final int end = mImpl.following(start);
117+
if (end == BreakIterator.DONE) {
118+
return null;
119+
}
121120
return getRange(start, end);
122121
}
123122

@@ -130,20 +129,20 @@ public int[] preceding(int offset) {
130129
if (offset <= 0) {
131130
return null;
132131
}
133-
int end = -1;
134-
if (offset > mText.length()) {
135-
offset = mText.length();
136-
if (mImpl.isBoundary(offset)) {
137-
end = offset;
138-
}
132+
int end = offset;
133+
if (end > textLegth) {
134+
end = textLegth;
139135
}
140-
if (end < 0) {
141-
end = mImpl.preceding(offset);
136+
while (!mImpl.isBoundary(end)) {
137+
end = mImpl.preceding(end);
138+
if (end == BreakIterator.DONE) {
139+
return null;
140+
}
142141
}
143-
if (end < 0) {
142+
final int start = mImpl.preceding(end);
143+
if (start == BreakIterator.DONE) {
144144
return null;
145145
}
146-
final int start = mImpl.preceding(end);
147146
return getRange(start, end);
148147
}
149148

@@ -195,25 +194,20 @@ public int[] following(int offset) {
195194
if (offset >= mText.length()) {
196195
return null;
197196
}
198-
int start = -1;
199-
if (offset < 0) {
200-
offset = 0;
201-
if (mImpl.isBoundary(offset) && isLetterOrDigit(offset)) {
202-
start = offset;
203-
}
204-
}
197+
int start = offset;
205198
if (start < 0) {
206-
while ((offset = mImpl.following(offset)) != DONE) {
207-
if (isLetterOrDigit(offset)) {
208-
start = offset;
209-
break;
210-
}
199+
start = 0;
200+
}
201+
while (!isLetterOrDigit(start) && !isStartBoundary(start)) {
202+
start = mImpl.following(start);
203+
if (start == BreakIterator.DONE) {
204+
return null;
211205
}
212206
}
213-
if (start < 0) {
207+
final int end = mImpl.following(start);
208+
if (end == BreakIterator.DONE || !isEndBoundary(end)) {
214209
return null;
215210
}
216-
final int end = mImpl.following(start);
217211
return getRange(start, end);
218212
}
219213

@@ -226,28 +220,33 @@ public int[] preceding(int offset) {
226220
if (offset <= 0) {
227221
return null;
228222
}
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-
}
223+
int end = offset;
224+
if (end > textLegth) {
225+
end = textLegth;
235226
}
236-
if (end < 0) {
237-
while ((offset = mImpl.preceding(offset)) != DONE) {
238-
if (offset > 0 && isLetterOrDigit(offset - 1)) {
239-
end = offset;
240-
break;
241-
}
227+
while (end > 0 && !isLetterOrDigit(end - 1) && !isEndBoundary(end)) {
228+
end = mImpl.preceding(end);
229+
if (end == BreakIterator.DONE) {
230+
return null;
242231
}
243232
}
244-
if (end < 0) {
233+
final int start = mImpl.preceding(end);
234+
if (start == BreakIterator.DONE || !isStartBoundary(start)) {
245235
return null;
246236
}
247-
final int start = mImpl.preceding(end);
248237
return getRange(start, end);
249238
}
250239

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+
251250
private boolean isLetterOrDigit(int index) {
252251
if (index >= 0 && index < mText.length()) {
253252
final int codePoint = mText.codePointAt(index);
@@ -276,31 +275,19 @@ public int[] following(int offset) {
276275
if (offset >= textLength) {
277276
return null;
278277
}
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-
}
278+
int start = offset;
290279
if (start < 0) {
291-
return null;
280+
start = 0;
292281
}
293-
while (start < textLength && mText.charAt(start) == '\n') {
282+
while (start < textLength && mText.charAt(start) == '\n'
283+
&& !isStartBoundary(start)) {
294284
start++;
295285
}
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-
}
286+
if (start >= textLength) {
287+
return null;
302288
}
303-
while (end < textLength && mText.charAt(end) == '\n') {
289+
int end = start + 1;
290+
while (end < textLength && !isEndBoundary(end)) {
304291
end++;
305292
}
306293
return getRange(start, end);
@@ -315,38 +302,31 @@ public int[] preceding(int offset) {
315302
if (offset <= 0) {
316303
return null;
317304
}
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-
}
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--;
331311
}
332312
if (end <= 0) {
333313
return null;
334314
}
335-
int start = end;
336-
while (start > 0 && mText.charAt(start - 1) == '\n') {
315+
int start = end - 1;
316+
while (start > 0 && !isStartBoundary(start)) {
337317
start--;
338318
}
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);
349319
return getRange(start, end);
350320
}
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+
}
351331
}
352332
}

core/java/android/view/View.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
15961596
/**
15971597
* @hide
15981598
*/
1599-
private int mAccessibilityCursorPosition = -1;
1599+
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
16001600

16011601
/**
16021602
* The view's tag.
@@ -2467,6 +2467,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
24672467
*/
24682468
public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004;
24692469

2470+
/**
2471+
* The undefined cursor position.
2472+
*/
2473+
private static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
2474+
24702475
/**
24712476
* Indicates that the screen has changed state and is now off.
24722477
*
@@ -6202,7 +6207,7 @@ public void clearAccessibilityFocus() {
62026207
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
62036208
notifyAccessibilityStateChanged();
62046209
// Clear the text navigation state.
6205-
setAccessibilityCursorPosition(-1);
6210+
setAccessibilityCursorPosition(ACCESSIBILITY_CURSOR_POSITION_UNDEFINED);
62066211
}
62076212
// Clear the global reference of accessibility focus if this
62086213
// view or any of its descendants had accessibility focus.
@@ -6252,6 +6257,7 @@ public boolean canTakeAccessibilityFocusFromHover() {
62526257
void clearAccessibilityFocusNoCallbacks() {
62536258
if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
62546259
mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
6260+
setAccessibilityCursorPosition(ACCESSIBILITY_CURSOR_POSITION_UNDEFINED);
62556261
invalidate();
62566262
}
62576263
}
@@ -6681,12 +6687,11 @@ private boolean nextAtGranularity(int granularity) {
66816687
final int current = getAccessibilityCursorPosition();
66826688
final int[] range = iterator.following(current);
66836689
if (range == null) {
6684-
setAccessibilityCursorPosition(-1);
66856690
return false;
66866691
}
66876692
final int start = range[0];
66886693
final int end = range[1];
6689-
setAccessibilityCursorPosition(start);
6694+
setAccessibilityCursorPosition(end);
66906695
sendViewTextTraversedAtGranularityEvent(
66916696
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
66926697
granularity, start, end);
@@ -6702,16 +6707,26 @@ private boolean previousAtGranularity(int granularity) {
67026707
if (iterator == null) {
67036708
return false;
67046709
}
6705-
final int selectionStart = getAccessibilityCursorPosition();
6706-
final int current = selectionStart >= 0 ? selectionStart : text.length() + 1;
6710+
int current = getAccessibilityCursorPosition();
6711+
if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
6712+
current = text.length();
6713+
} else if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
6714+
// When traversing by character we always put the cursor after the character
6715+
// to ease edit and have to compensate before asking the for previous segment.
6716+
current--;
6717+
}
67076718
final int[] range = iterator.preceding(current);
67086719
if (range == null) {
6709-
setAccessibilityCursorPosition(-1);
67106720
return false;
67116721
}
67126722
final int start = range[0];
67136723
final int end = range[1];
6714-
setAccessibilityCursorPosition(end);
6724+
// Always put the cursor after the character to ease edit.
6725+
if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
6726+
setAccessibilityCursorPosition(end);
6727+
} else {
6728+
setAccessibilityCursorPosition(start);
6729+
}
67156730
sendViewTextTraversedAtGranularityEvent(
67166731
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
67176732
granularity, start, end);

0 commit comments

Comments
 (0)