diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/AccessibilityTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/AccessibilityTabHelper.java index f2fa5cde28ac3..b75b3a2077eda 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/AccessibilityTabHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/AccessibilityTabHelper.java @@ -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 diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/AutoDisableAccessibilityHandler.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/AutoDisableAccessibilityHandler.java index ba213b11d0369..bd55b84253add 100644 --- a/content/public/android/java/src/org/chromium/content/browser/accessibility/AutoDisableAccessibilityHandler.java +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/AutoDisableAccessibilityHandler.java @@ -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; + } } diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java index 177f9c5ba4e3f..d647a4ea4e70f 100644 --- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java @@ -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) { diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsAccessibility.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsAccessibility.java index 8c70fce250b4d..d59cf1ef70b3f 100644 --- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsAccessibility.java +++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsAccessibility.java @@ -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. diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java index 2eaf0a0fc8c7e..702d9ff259a37 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java @@ -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 ------------------ // /** diff --git a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java index 6e366417cad8a..4b5c568211918 100644 --- a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java +++ b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java @@ -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); }