0

[Auto-disable Accessibility] Disable feature for WebView and CCT embedders

This CL continues the Auto-disable Accessibility feature work.

With this CL we disable the feature for CCT and WebViews. We also
update the AccessibilityState to log changes to the state for
convenience when debugging.

AX-Relnotes: N/A
Bug: 1430202, b/265493191
Change-Id: I17cc21297bfc245cb22cfec2d0a1280b82eac5dc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4742969
Reviewed-by: Amanda Lin Dietz <aldietz@google.com>
Reviewed-by: Jinsuk Kim <jinsukkim@chromium.org>
Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Mark Schillaci <mschillaci@google.com>
Cr-Commit-Position: refs/heads/main@{#1181194}
This commit is contained in:
Mark Schillaci
2023-08-08 22:24:40 +00:00
committed by Chromium LUCI CQ
parent 4915ff15bf
commit c39c7d2fdf
6 changed files with 76 additions and 1 deletions
chrome/android/java/src/org/chromium/chrome/browser/accessibility
content/public/android
java
javatests
src
org
chromium
content
ui/accessibility/android/java/src/org/chromium/ui/accessibility

@@ -57,6 +57,9 @@ public class AccessibilityTabHelper extends EmptyTabObserver implements UserData
// Enable image descriptions feature normally, but not for Chrome Custom Tabs.
wcax.setIsImageDescriptionsCandidate(!tab.isCustomTab());
// Enable Auto-disable Accessibility feature normally, but not for Chrome Custom Tabs.
wcax.setIsAutoDisableAccessibilityCandidate(!tab.isCustomTab());
}
@Override

@@ -6,6 +6,8 @@ package org.chromium.content.browser.accessibility;
import android.view.View;
import androidx.annotation.VisibleForTesting;
/**
* Helper class that handles the logic and state behind the "Auto Disable" accessibility feature.
* Clients need to cancel/reset the timer based on their implementation (e.g. on a user action).
@@ -66,4 +68,12 @@ public class AutoDisableAccessibilityHandler {
mClient.onDisabled();
mHasPendingTimer = false;
}
/**
* Return true when there is a pending timer.
*/
@VisibleForTesting
public boolean hasPendingTimer() {
return mHasPendingTimer;
}
}

@@ -222,6 +222,7 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa
private final AutoDisableAccessibilityHandler mAutoDisableAccessibilityHandler;
private boolean mIsCurrentlyAutoDisabled;
private int mAutoDisableUsageCounter;
private boolean mIsAutoDisableAccessibilityCandidate;
/**
* Create a WebContentsAccessibilityImpl object.
@@ -509,6 +510,15 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa
ResettersForTesting.register(() -> mTracker = oldValue);
}
public void setIsAutoDisableAccessibilityCandidateForTesting(
boolean isAutoDisableAccessibilityCandidate) {
mIsAutoDisableAccessibilityCandidate = isAutoDisableAccessibilityCandidate;
}
public boolean hasAnyPendingTimersForTesting() {
return mAutoDisableAccessibilityHandler.hasPendingTimer();
}
public void signalEndOfTestForTesting() {
WebContentsAccessibilityImplJni.get().signalEndOfTestForTesting(mNativeObj);
}
@@ -705,7 +715,8 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa
// disabled then re-enabled the renderer multiple times for this instance, then we
// will return early and keep accessibility enabled to prevent further churn.
if (ContentFeatureMap.isEnabled(ContentFeatureList.AUTO_DISABLE_ACCESSIBILITY_V2)) {
if (mAutoDisableUsageCounter >= AUTO_DISABLE_SINGLE_INSTANCE_TOGGLE_LIMIT) {
if (mAutoDisableUsageCounter >= AUTO_DISABLE_SINGLE_INSTANCE_TOGGLE_LIMIT
|| !mIsAutoDisableAccessibilityCandidate) {
mAutoDisableAccessibilityHandler.cancelDisableTimer();
return;
}
@@ -955,6 +966,12 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa
mIsImageDescriptionsCandidate = isImageDescriptionsCandidate;
}
@Override
public void setIsAutoDisableAccessibilityCandidate(
boolean isAutoDisableAccessibilityCandidate) {
mIsAutoDisableAccessibilityCandidate = isAutoDisableAccessibilityCandidate;
}
@Override
public void onProvideVirtualStructure(
final ViewStructure structure, final boolean ignoreScrollOffset) {

@@ -85,6 +85,12 @@ public interface WebContentsAccessibility {
*/
void setIsImageDescriptionsCandidate(boolean isImageDescriptionsCandidate);
/**
* Sets whether or not this instance is a candidate for the auto-disable accessibility feature,
* if it is enabled. This feature is dependent on embedder behavior and accessibility state.
*/
void setIsAutoDisableAccessibilityCandidate(boolean isAutoDisableAccessibilityCandidate);
/**
* Called when autofill popup is displayed. Used to upport navigation through the view.
* @param autofillPopupView The displayed autofill popup view.

@@ -161,6 +161,8 @@ public class WebContentsAccessibilityTest {
private static final Map<String, Boolean> ON_DEMAND_ON_AXMODES_ON =
Map.of(ContentFeatureList.ON_DEMAND_ACCESSIBILITY_EVENTS, true,
ContentFeatureList.ACCESSIBILITY_PERFORMANCE_FILTERING, true);
private static final Map<String, Boolean> AUTO_DISABLE_V2_ON =
Map.of(ContentFeatureList.AUTO_DISABLE_ACCESSIBILITY_V2, true);
// Constant values for unit tests
private static final int UNSUPPRESSED_EXPECTED_COUNT = 15;
@@ -745,6 +747,27 @@ public class WebContentsAccessibilityTest {
() -> createAccessibilityNodeInfo(vvid2).isAccessibilityFocused());
}
/**
* Tests that Auto-disable Accessibility timers are not set for instances that are not
* candidates for the feature (e.g. WebView, CCT).
*/
@Test
@SmallTest
public void testAutoDisableAccessibility_candidatesCheck() throws Throwable {
setupTestWithHTML("<p>This is a test</p>");
waitForNodeMatching(sTextMatcher, "This is a test");
// Enable feature, but set this instance as not a candidate.
FeatureList.setTestFeatures(AUTO_DISABLE_V2_ON);
mActivityTestRule.mWcax.setIsAutoDisableAccessibilityCandidateForTesting(false);
// Changing the accessibility state will refresh the native state.
TestThreadUtils.runOnUiThreadBlocking(
() -> { AccessibilityState.setIsTextShowPasswordEnabledForTesting(true); });
Assert.assertFalse(mActivityTestRule.mWcax.hasAnyPendingTimersForTesting());
}
// ------------------ Tests of AccessibilityNodeInfo caching mechanism ------------------ //
/**

@@ -25,6 +25,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.base.ActivityState;
@@ -128,6 +129,20 @@ public class AccessibilityState {
this.isTextShowPasswordEnabled = isTextShowPasswordEnabled;
this.isOnlyPasswordManagersEnabled = isOnlyPasswordManagersEnabled;
}
@NonNull
@Override
public String toString() {
return "State{"
+ "isScreenReaderEnabled=" + isScreenReaderEnabled
+ ", isTouchExplorationEnabled=" + isTouchExplorationEnabled
+ ", isPerformGesturesEnabled=" + isPerformGesturesEnabled
+ ", isAnyAccessibilityServiceEnabled=" + isAnyAccessibilityServiceEnabled
+ ", isAccessibilityToolPresent=" + isAccessibilityToolPresent
+ ", isSpokenFeedbackServicePresent=" + isSpokenFeedbackServicePresent
+ ", isTextShowPasswordEnabled=" + isTextShowPasswordEnabled
+ ", isOnlyPasswordManagersEnabled=" + isOnlyPasswordManagersEnabled + '}';
}
}
// Analysis of the most popular accessibility services on Android suggests
@@ -543,6 +558,7 @@ public class AccessibilityState {
State oldState = sState;
sState = newState;
Log.v(TAG, "New AccessibilityState: " + sState.toString());
for (Listener listener : sListeners) {
listener.onAccessibilityStateChanged(oldState, newState);
}