|
13 | 13 | import android.text.InputFilter;
|
14 | 14 | import android.text.InputType;
|
15 | 15 | import android.text.Layout;
|
| 16 | +import android.text.Selection; |
16 | 17 | import android.text.SpanWatcher;
|
17 | 18 | import android.text.Spannable;
|
18 | 19 | import android.text.SpannableStringBuilder;
|
|
25 | 26 | import android.view.KeyEvent;
|
26 | 27 | import android.view.MotionEvent;
|
27 | 28 | import android.view.View;
|
| 29 | +import android.view.accessibility.AccessibilityEvent; |
28 | 30 | import android.view.inputmethod.EditorInfo;
|
29 | 31 | import android.view.inputmethod.ExtractedText;
|
30 | 32 | import android.view.inputmethod.ExtractedTextRequest;
|
@@ -399,6 +401,77 @@ public void setTokenLimit(int tokenLimit) {
|
399 | 401 | */
|
400 | 402 | abstract protected T defaultObject(String completionText);
|
401 | 403 |
|
| 404 | + /** |
| 405 | + * Correctly build accessibility string for token contents |
| 406 | + * |
| 407 | + * This seems to be a hidden API, but there doesn't seem to be another reasonable way |
| 408 | + * @return custom string for accessibility |
| 409 | + */ |
| 410 | + @SuppressWarnings("unused") |
| 411 | + public CharSequence getTextForAccessibility() { |
| 412 | + if (getObjects().size() == 0) { |
| 413 | + return getText(); |
| 414 | + } |
| 415 | + |
| 416 | + SpannableStringBuilder description = new SpannableStringBuilder(); |
| 417 | + Editable text = getText(); |
| 418 | + int selectionStart = -1; |
| 419 | + int selectionEnd = -1; |
| 420 | + int i; |
| 421 | + //Need to take the existing tet buffer and |
| 422 | + // - replace all tokens with a decent string representation of the object |
| 423 | + // - set the selection span to the corresponding location in the new CharSequence |
| 424 | + for (i = 0; i < text.length(); ++i) { |
| 425 | + //See if this is where we should start the selection |
| 426 | + int origSelectionStart = Selection.getSelectionStart(text); |
| 427 | + if (i == origSelectionStart) { |
| 428 | + selectionStart = description.length(); |
| 429 | + } |
| 430 | + int origSelectionEnd = Selection.getSelectionEnd(text); |
| 431 | + if (i == origSelectionEnd) { |
| 432 | + selectionEnd = description.length(); |
| 433 | + } |
| 434 | + |
| 435 | + //Replace token spans |
| 436 | + TokenImageSpan[] tokens = text.getSpans(i, i, TokenImageSpan.class); |
| 437 | + if (tokens.length > 0) { |
| 438 | + TokenImageSpan token = tokens[0]; |
| 439 | + description = description.append(tokenizer.terminateToken(token.getToken().toString())); |
| 440 | + i = text.getSpanEnd(token); |
| 441 | + continue; |
| 442 | + } |
| 443 | + |
| 444 | + description = description.append(text.subSequence(i, i + 1)); |
| 445 | + } |
| 446 | + |
| 447 | + int origSelectionStart = Selection.getSelectionStart(text); |
| 448 | + if (i == origSelectionStart) { |
| 449 | + selectionStart = description.length(); |
| 450 | + } |
| 451 | + int origSelectionEnd = Selection.getSelectionEnd(text); |
| 452 | + if (i == origSelectionEnd) { |
| 453 | + selectionEnd = description.length(); |
| 454 | + } |
| 455 | + |
| 456 | + if (selectionStart >= 0 && selectionEnd >= 0) { |
| 457 | + Selection.setSelection(description, selectionStart, selectionEnd); |
| 458 | + } |
| 459 | + |
| 460 | + return description; |
| 461 | + } |
| 462 | + |
| 463 | + @Override |
| 464 | + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { |
| 465 | + super.onInitializeAccessibilityEvent(event); |
| 466 | + |
| 467 | + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { |
| 468 | + CharSequence text = getTextForAccessibility(); |
| 469 | + event.setFromIndex(Selection.getSelectionStart(text)); |
| 470 | + event.setToIndex(Selection.getSelectionEnd(text)); |
| 471 | + event.setItemCount(text.length()); |
| 472 | + } |
| 473 | + } |
| 474 | + |
402 | 475 | private int getCorrectedTokenEnd() {
|
403 | 476 | Editable editable = getText();
|
404 | 477 | int cursorPosition = getSelectionEnd();
|
|
0 commit comments