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

Commit 53b8cf32 authored by Lucy Chang's avatar Lucy Chang Committed by Android (Google) Code Review
Browse files

Merge "Don't send AccessibilityEent if text is unchanged"

parents 13288572 5f759864
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -202,6 +202,7 @@ import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -6316,6 +6317,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            text = TextUtils.stringOrSpannedString(text);
        }
        @AccessibilityUtils.A11yTextChangeType int a11yTextChangeType = AccessibilityUtils.NONE;
        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            a11yTextChangeType = AccessibilityUtils.textOrSpanChanged(text, mText);
        }
        if (mAutoLinkMask != 0) {
            Spannable s2;
@@ -6335,6 +6341,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                 * setText() again to try to upgrade the buffer type.
                 */
                setTextInternal(text);
                if (a11yTextChangeType == AccessibilityUtils.NONE) {
                    a11yTextChangeType = AccessibilityUtils.PARCELABLE_SPAN;
                }
                // Do not change the movement method for text that support text selection as it
                // would prevent an arbitrary cursor displacement.
@@ -6399,7 +6408,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        sendOnTextChanged(text, 0, oldlen, textLength);
        onTextChanged(text, 0, oldlen, textLength);
        notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
        if (a11yTextChangeType == AccessibilityUtils.TEXT) {
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
        } else if (a11yTextChangeType == AccessibilityUtils.PARCELABLE_SPAN) {
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
        }
        if (needEditableForNotification) {
            sendAfterTextChanged((Editable) text);
+77 −1
Original line number Diff line number Diff line
@@ -20,16 +20,23 @@ import static com.android.internal.accessibility.common.ShortcutConstants.Access
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.ParcelableSpan;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;

import libcore.util.EmptyArray;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -39,7 +46,25 @@ import java.util.Set;
 * Collection of utilities for accessibility service.
 */
public final class AccessibilityUtils {
    private AccessibilityUtils() {}
    private AccessibilityUtils() {
    }

    /** @hide */
    @IntDef(value = {
            NONE,
            TEXT,
            PARCELABLE_SPAN
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface A11yTextChangeType {
    }

    /** Specifies no content has been changed for accessibility. */
    public static final int NONE = 0;
    /** Specifies some readable sequence has been changed. */
    public static final int TEXT = 1;
    /** Specifies some parcelable spans has been changed. */
    public static final int PARCELABLE_SPAN = 2;

    /**
     * Returns the set of enabled accessibility services for userId. If there are no
@@ -168,4 +193,55 @@ public final class AccessibilityUtils {
                Settings.Secure.USER_SETUP_COMPLETE, /* def= */ 0, UserHandle.USER_CURRENT)
                != /* false */ 0;
    }

    /**
     * Returns the text change type for accessibility. It only cares about readable sequence changes
     * or {@link ParcelableSpan} changes which are able to pass via IPC.
     *
     * @param before The CharSequence before changing
     * @param after  The CharSequence after changing
     * @return Returns {@code TEXT} for readable sequence changes or {@code PARCELABLE_SPAN} for
     * ParcelableSpan changes. Otherwise, returns {@code NONE}.
     */
    @A11yTextChangeType
    public static int textOrSpanChanged(CharSequence before, CharSequence after) {
        if (!TextUtils.equals(before, after)) {
            return TEXT;
        }
        if (before instanceof Spanned || after instanceof Spanned) {
            if (!parcelableSpansEquals(before, after)) {
                return PARCELABLE_SPAN;
            }
        }
        return NONE;
    }

    private static boolean parcelableSpansEquals(CharSequence before, CharSequence after) {
        Object[] spansA = EmptyArray.OBJECT;
        Object[] spansB = EmptyArray.OBJECT;
        Spanned a = null;
        Spanned b = null;
        if (before instanceof Spanned) {
            a = (Spanned) before;
            spansA = a.getSpans(0, a.length(), ParcelableSpan.class);
        }
        if (after instanceof Spanned) {
            b = (Spanned) after;
            spansB = b.getSpans(0, b.length(), ParcelableSpan.class);
        }
        if (spansA.length != spansB.length) {
            return false;
        }
        for (int i = 0; i < spansA.length; ++i) {
            final Object thisSpan = spansA[i];
            final Object otherSpan = spansB[i];
            if ((thisSpan.getClass() != otherSpan.getClass())
                    || (a.getSpanStart(thisSpan) != b.getSpanStart(otherSpan))
                    || (a.getSpanEnd(thisSpan) != b.getSpanEnd(otherSpan))
                    || (a.getSpanFlags(thisSpan) != b.getSpanFlags(otherSpan))) {
                return false;
            }
        }
        return true;
    }
}
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.accessibility;

import static junit.framework.Assert.assertEquals;

import android.text.ParcelableSpan;
import android.text.SpannableString;
import android.text.style.LocaleSpan;

import androidx.test.runner.AndroidJUnit4;

import com.android.internal.accessibility.util.AccessibilityUtils;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Locale;

/**
 * Unit tests for AccessibilityUtils.
 */
@RunWith(AndroidJUnit4.class)
public class AccessibilityUtilsTest {
    @Test
    public void textOrSpanChanged_stringChange_returnTextChange() {
        final CharSequence beforeText = "a";

        final CharSequence afterText = "b";

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.TEXT, type);
    }

    @Test
    public void textOrSpanChanged_stringNotChange_returnNoneChange() {
        final CharSequence beforeText = "a";

        final CharSequence afterText = "a";

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
    }

    @Test
    public void textOrSpanChanged_nonSpanToNonParcelableSpan_returnNoneChange() {
        final Object nonParcelableSpan = new Object();
        final CharSequence beforeText = new SpannableString("a");

        final SpannableString afterText = new SpannableString("a");
        afterText.setSpan(nonParcelableSpan, 0, 1, 0);

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
    }

    @Test
    public void textOrSpanChanged_nonSpanToParcelableSpan_returnParcelableSpanChange() {
        final ParcelableSpan parcelableSpan = new LocaleSpan(Locale.ENGLISH);
        final CharSequence beforeText = new SpannableString("a");

        final SpannableString afterText = new SpannableString("a");
        afterText.setSpan(parcelableSpan, 0, 1, 0);

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
    }

    @Test
    public void textOrSpanChanged_nonParcelableSpanToParcelableSpan_returnParcelableSpanChange() {
        final Object nonParcelableSpan = new Object();
        final ParcelableSpan parcelableSpan = new LocaleSpan(Locale.ENGLISH);
        final SpannableString beforeText = new SpannableString("a");
        beforeText.setSpan(nonParcelableSpan, 0, 1, 0);

        SpannableString afterText = new SpannableString("a");
        afterText.setSpan(parcelableSpan, 0, 1, 0);

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
    }

    @Test
    public void textOrSpanChanged_nonParcelableSpanChange_returnNoneChange() {
        final Object nonParcelableSpan = new Object();
        final SpannableString beforeText = new SpannableString("a");
        beforeText.setSpan(nonParcelableSpan, 0, 1, 0);

        final SpannableString afterText = new SpannableString("a");
        afterText.setSpan(nonParcelableSpan, 1, 1, 0);

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
    }

    @Test
    public void textOrSpanChanged_parcelableSpanChange_returnParcelableSpanChange() {
        final ParcelableSpan parcelableSpan = new LocaleSpan(Locale.ENGLISH);
        final SpannableString beforeText = new SpannableString("a");
        beforeText.setSpan(parcelableSpan, 0, 1, 0);

        final SpannableString afterText = new SpannableString("a");
        afterText.setSpan(parcelableSpan, 1, 1, 0);

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
    }
}