From c39c7d2fdf1e005f12d2e9340bc1e186ca5c213f Mon Sep 17 00:00:00 2001
From: Mark Schillaci <mschillaci@google.com>
Date: Tue, 8 Aug 2023 22:24:40 +0000
Subject: [PATCH] [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}
---
 .../accessibility/AccessibilityTabHelper.java |  3 +++
 .../AutoDisableAccessibilityHandler.java      | 10 ++++++++
 .../WebContentsAccessibilityImpl.java         | 19 ++++++++++++++-
 .../browser/WebContentsAccessibility.java     |  6 +++++
 .../WebContentsAccessibilityTest.java         | 23 +++++++++++++++++++
 .../ui/accessibility/AccessibilityState.java  | 16 +++++++++++++
 6 files changed, 76 insertions(+), 1 deletion(-)

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);
         }