Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 63ab661d authored by yingleiw's avatar yingleiw
Browse files

Send TYPE_VIEW_TEXT_CHANGED a11y event when SuggestionSpan is added

When user types space after a word, we receive a text
change event, but the suggestion span is not added to
the text yet. The spell checker adds the span after the
text change event is sent. Previously we send the event
in onSpanAdded in TextView, but we don't do anything or
update the before text for span removed. This is a bit
confusing and error prone. This change moves the send
event logic into spell checker.

Bug: b/143378480

Test: tested with talkback.
Change-Id: Ibd45843494304602b177df8da520a51058989f10
parent e551c20d
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
@@ -416,7 +417,15 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    }
                    if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
                            && end > start) {
                        removeErrorSuggestionSpan(editable, start, end, RemoveReason.OBSOLETE);
                        boolean visibleToAccessibility = mTextView.isVisibleToAccessibility();
                        CharSequence beforeText =
                                visibleToAccessibility ? new SpannedString(editable) : null;
                        boolean spanRemoved = removeErrorSuggestionSpan(
                                editable, start, end, RemoveReason.OBSOLETE);
                        if (visibleToAccessibility && spanRemoved) {
                            mTextView.sendAccessibilityEventTypeViewTextChanged(
                                    beforeText, start, end);
                        }
                    }
                }
                return spellCheckSpan;
@@ -437,8 +446,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
        OBSOLETE,
    }

    private static void removeErrorSuggestionSpan(
    private static boolean removeErrorSuggestionSpan(
            Editable editable, int start, int end, RemoveReason reason) {
        boolean spanRemoved = false;
        SuggestionSpan[] spans = editable.getSpans(start, end, SuggestionSpan.class);
        for (SuggestionSpan span : spans) {
            if (editable.getSpanStart(span) == start
@@ -450,8 +460,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
                            + editable.subSequence(start, end) + ", reason: " + reason);
                }
                editable.removeSpan(span);
                spanRemoved = true;
            }
        }
        return spanRemoved;
    }

    @Override
@@ -568,8 +580,13 @@ public class SpellChecker implements SpellCheckerSessionListener {
        }
        SuggestionSpan suggestionSpan =
                new SuggestionSpan(mTextView.getContext(), suggestions, flags);
        removeErrorSuggestionSpan(editable, start, end, RemoveReason.REPLACE);
        boolean spanRemoved = removeErrorSuggestionSpan(editable, start, end, RemoveReason.REPLACE);
        boolean sendAccessibilityEvent = !spanRemoved && mTextView.isVisibleToAccessibility();
        CharSequence beforeText = sendAccessibilityEvent ? new SpannedString(editable) : null;
        editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        if (sendAccessibilityEvent) {
            mTextView.sendAccessibilityEventTypeViewTextChanged(beforeText, start, end);
        }

        mTextView.invalidateRegion(start, end, false /* No cursor involved */);
    }
+17 −51
Original line number Diff line number Diff line
@@ -12501,6 +12501,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return TextUtils.trimToParcelableSize(mTransformed);
    }
    boolean isVisibleToAccessibility() {
        return AccessibilityManager.getInstance(mContext).isEnabled()
                && (isFocused() || (isSelected() && isShown()));
    }
    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
            int fromIndex, int removedCount, int addedCount) {
        AccessibilityEvent event =
@@ -12512,6 +12517,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        sendAccessibilityEventUnchecked(event);
    }
    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
            int fromIndex, int toIndex) {
        AccessibilityEvent event =
                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
        event.setFromIndex(fromIndex);
        event.setToIndex(toIndex);
        event.setBeforeText(beforeText);
        sendAccessibilityEventUnchecked(event);
    }
    private InputMethodManager getInputMethodManager() {
        return getContext().getSystemService(InputMethodManager.class);
    }
@@ -13826,10 +13841,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            }
            TextView.this.handleTextChanged(buffer, start, before, after);
            if (AccessibilityManager.getInstance(mContext).isEnabled()
                    && (isFocused() || (isSelected() && isShown()))) {
            if (isVisibleToAccessibility()) {
                sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
                mBeforeText = TextUtils.stringOrSpannedString(mTransformed);
                mBeforeText = null;
            }
        }
@@ -13857,54 +13871,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
            }
            TextView.this.spanChange(buf, what, -1, s, -1, e);
            // Note we don't update mBeforeText here. We look for SuggestionSpans added after the
            // text content changes.
            if (AccessibilityManager.getInstance(mContext).isEnabled()
                    && (isFocused() || (isSelected() && isShown()))
                    && (what instanceof SuggestionSpan)) {
                // When the user types a new word, and SuggestionSpans on the existing words will be
                // removed and added again. We don't need to send out events for existing
                // SuggestionSpans. Multiple spans can be placed on the range.
                if (mBeforeText instanceof SpannedString) {
                    final SpannedString beforeSpannedString = (SpannedString) mBeforeText;
                    if ((beforeSpannedString.getSpanStart(what) == s)
                            && (beforeSpannedString.getSpanEnd(what) == e)) {
                        // Exactly same span is found.
                        return;
                    }
                    // Suggestion span couldn't be found. Try to find a suggestion span that has the
                    // same contents.
                    SuggestionSpan[] suggestionSpans = beforeSpannedString.getSpans(s, e,
                            SuggestionSpan.class);
                    for (final SuggestionSpan suggestionSpan : suggestionSpans) {
                        final int start = beforeSpannedString.getSpanStart(suggestionSpan);
                        if (start != s) {
                            continue;
                        }
                        final int end = beforeSpannedString.getSpanEnd(suggestionSpan);
                        if (end != e) {
                            continue;
                        }
                        if (equalSuggestionSpan(suggestionSpan, (SuggestionSpan) what)) {
                            return;
                        }
                    }
                }
                AccessibilityEvent event =
                        AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
                event.setFromIndex(s);
                event.setToIndex(e);
                event.setBeforeText(mBeforeText);
                sendAccessibilityEventUnchecked(event);
            }
        }
        private boolean equalSuggestionSpan(SuggestionSpan span1, SuggestionSpan span2) {
            // We compare flags because flags will determine the underline color.
            return Arrays.equals(span1.getSuggestions(), span2.getSuggestions())
                    && Objects.equals(span1.getLocaleObject(), span2.getLocaleObject())
                    && span1.getLocale().equals(span2.getLocale())
                    && (span1.getFlags() == span2.getFlags());
        }
        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {