0

Reland "[Auto-disable Accessibility] Point ChromeAccessibilityUtil to AccessibilityState"

This is a reland of commit 8d93191c13

Original change's description:
> [Auto-disable Accessibility] Point ChromeAccessibilityUtil to AccessibilityState
>
> This CL continues the work to consolidate all accessibility state
> management in Clank to a single source, AccessibilityState.java.
>
> With this CL we point the ChromeAccessibilityUtil to use
> AccessibilityState so that under-the-hood all parts of the Clank
> code-base are running off the same accessibility state engine.
> We do not remove the ChromeAccessibilityUtil to keep the code
> change small, and after some general usage and manual testing
> we will follow-up with a series of CLs to completely remove
> the ChromeAccessibilityUtil class.
>
> The implementation in AccessibilityState matches the previous
> existing impl of ChromeAccessibilityUtil, i.e. checking if
> accessibility is enabled is equivalent to asking: "is there a
> service that uses touch exploration enabled, OR, is there a
> service that requested to perform gestures enabled." This is
> subtly different than what AccessibilityState will consider as
> "enabled" in the future, since it provides more granularity in
> state information. As we move to AccessibilityState, we will
> audit usages and see if a more granular check can be applied on
> a case-by-case basis.
>
> AX-Relnotes: N/A
> Bug: 1430202, b/265493191
> Change-Id: I5757a4d2dc426a9c58107de4ca56ccf456d33968
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4753000
> Commit-Queue: Mark Schillaci <mschillaci@google.com>
> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
> Reviewed-by: Theresa Sullivan <twellington@chromium.org>
> Reviewed-by: Bo Liu <boliu@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1183347}

Bug: 1430202, b/265493191
Change-Id: I5f804c2b238db790ba8adaf70287da516761ae0e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4781009
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Mark Schillaci <mschillaci@google.com>
Reviewed-by: Theresa Sullivan <twellington@chromium.org>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: Bo Liu <boliu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1184715}
This commit is contained in:
Mark Schillaci
2023-08-17 14:52:33 +00:00
committed by Chromium LUCI CQ
parent d5c8e1cbc9
commit ecd39ec12c
8 changed files with 71 additions and 211 deletions
chrome
android
java
src
org
chromium
javatests
src
org
chromium
chrome
browser
compositor
browser
util
BUILD.gn
android
java
src
org
chromium
ui
accessibility
android
java
src
org
chromium
android
java
src
org
weblayer/browser/java/org/chromium/weblayer_private

@ -1457,18 +1457,6 @@ public class ChromeTabbedActivity extends ChromeActivity<ChromeActivityComponent
@Override
public void onAccessibilityModeChanged(boolean enabled) {
if (mIsAccessibilityTabSwitcherEnabled != null) {
// TODO(https://crbug.com/1455234): This is a temporary solution to prevent a crash when
// toggling a11y state (e.g. through TalkBack) while using tab groups in the grid tab
// switcher and the legacy a11y list switcher. When TabGroupsContinuationAndroid
// launches, we can clean up the legacy a11y switcher along with this check.
if (isTablet()) {
if (getTabReparentingControllerSupplier().get() != null) {
getTabReparentingControllerSupplier().get().prepareTabsForReparenting();
}
recreate();
}
}
onAccessibilityTabSwitcherModeChanged();
}

@ -469,12 +469,6 @@ public class LayoutManagerTest implements MockTabModelDelegate {
public void testTabSwitcherLayout_Enabled_HighEndPhone() throws Exception {
// clang-format on
verifyTabSwitcherLayoutEnable(TabListCoordinator.TabListMode.GRID);
// Verify accessibility
TabUiTestHelper.finishActivity(mActivityTestRule.getActivity());
DeviceClassManager.GTS_ACCESSIBILITY_SUPPORT.setForTesting(true);
setAccessibilityEnabledForTesting(true);
verifyTabSwitcherLayoutEnable(TabListCoordinator.TabListMode.GRID);
}
@Test

@ -24,6 +24,7 @@ android_library("java") {
"//components/embedder_support/android:util_java",
"//third_party/androidx:androidx_annotation_annotation_java",
"//third_party/androidx:androidx_core_core_java",
"//ui/accessibility:ax_base_java",
"//ui/android:ui_full_java",
"//url:gurl_java",
]

@ -4,12 +4,9 @@
package org.chromium.chrome.browser.util;
import android.app.Activity;
import androidx.annotation.Nullable;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.util.AccessibilityUtil;
/**
@ -17,56 +14,26 @@ import org.chromium.ui.util.AccessibilityUtil;
*/
public class ChromeAccessibilityUtil extends AccessibilityUtil {
private static ChromeAccessibilityUtil sInstance;
private ActivityStateListenerImpl mActivityStateListener;
private boolean mWasAccessibilityEnabledForTestingCalled;
private boolean mWasTouchExplorationEnabledForTestingCalled;
private final class ActivityStateListenerImpl
implements ApplicationStatus.ActivityStateListener {
@Override
public void onActivityStateChange(Activity activity, int newState) {
// If an activity is being resumed, it's possible the user changed accessibility
// settings while not in a Chrome activity. Recalculate isAccessibilityEnabled()
// and notify observers if necessary. If all activities are destroyed, remove the
// activity state listener to avoid leaks.
if (ApplicationStatus.isEveryActivityDestroyed()) {
stopTrackingStateAndRemoveObservers();
} else if (!mWasAccessibilityEnabledForTestingCalled
&& !mWasTouchExplorationEnabledForTestingCalled
&& newState == ActivityState.RESUMED) {
updateIsAccessibilityEnabledAndNotify();
}
}
};
public static ChromeAccessibilityUtil get() {
if (sInstance == null) sInstance = new ChromeAccessibilityUtil();
if (sInstance == null) {
sInstance = new ChromeAccessibilityUtil();
AccessibilityState.addListener(sInstance);
}
return sInstance;
}
private ChromeAccessibilityUtil() {}
@Override
protected void stopTrackingStateAndRemoveObservers() {
super.stopTrackingStateAndRemoveObservers();
if (mActivityStateListener != null) {
ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
mActivityStateListener = null;
}
}
@Override
@Deprecated
public boolean isAccessibilityEnabled() {
if (mActivityStateListener == null) {
mActivityStateListener = new ActivityStateListenerImpl();
ApplicationStatus.registerStateListenerForAllActivities(mActivityStateListener);
}
return super.isAccessibilityEnabled();
return AccessibilityState.isAccessibilityEnabled();
}
@Override
public void setAccessibilityEnabledForTesting(@Nullable Boolean isEnabled) {
mWasAccessibilityEnabledForTestingCalled = isEnabled != null;
AccessibilityState.setIsPerformGesturesEnabledForTesting(Boolean.TRUE.equals(isEnabled));
AccessibilityState.setIsTouchExplorationEnabledForTesting(Boolean.TRUE.equals(isEnabled));
super.setAccessibilityEnabledForTesting(isEnabled);
}
}

@ -183,6 +183,7 @@ public class AccessibilityState {
private static State sState;
private static boolean sInitialized;
private static boolean sIsInTestingMode;
private static Boolean sPreInitCachedValuePerformGesturesEnabled;
// Observers for various System, Activity, and Settings states relevant to accessibility.
private static final ApplicationStatus.ActivityStateListener sActivityStateListener =
@ -249,7 +250,28 @@ public class AccessibilityState {
}
public static boolean isPerformGesturesEnabled() {
if (!sInitialized) updateAccessibilityServices();
if (!sInitialized) {
if (sPreInitCachedValuePerformGesturesEnabled != null) {
return sPreInitCachedValuePerformGesturesEnabled;
}
fetchAccessibilityManager();
if (sAccessibilityManager.isEnabled()) {
for (AccessibilityServiceInfo service :
sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK)) {
if ((service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES)
!= 0) {
sPreInitCachedValuePerformGesturesEnabled = true;
return true;
}
}
}
sPreInitCachedValuePerformGesturesEnabled = false;
return false;
}
return sState.isPerformGesturesEnabled;
}
@ -296,6 +318,25 @@ public class AccessibilityState {
return sAccessibilitySpeakPasswordEnabled;
}
/**
* Helper method to return the value that is equivalent to the deprecated approach:
* ChromeAccessibilityUtil.get().isAccessibilityEnabled()
*
* Avoid calling this method at all costs. The naming of this method is misleading and its
* usage is tricky. Use the more granular methods of this class.
*
* Returns true if an accessibility service is running that uses touch exploration OR a service
* is running that can perform gestures.
*
* @return true when touch exploration or gesture performing services are running.
*/
// TODO(mschillaci): Replace all calls of this method with newer approach.
@Deprecated
public static boolean isAccessibilityEnabled() {
return AccessibilityState.isTouchExplorationEnabled()
|| AccessibilityState.isPerformGesturesEnabled();
}
/**
* Convenience method to get a recommended timeout on all versions of Android. The method that
* is part of AccessibilityManager is only available on Android >= Q. For earlier versions of

@ -4,27 +4,17 @@
package org.chromium.ui.util;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import java.util.List;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.accessibility.AccessibilityState.State;
/**
* Exposes information about the current accessibility state.
*/
public class AccessibilityUtil {
public class AccessibilityUtil implements AccessibilityState.Listener {
/**
* An observer to be notified of accessibility status changes.
*/
@ -38,140 +28,38 @@ public class AccessibilityUtil {
void onAccessibilityModeChanged(boolean enabled);
}
// Cached value of isAccessibilityEnabled(). If null, indicates the value needs to be
// recalculated.
private Boolean mIsAccessibilityEnabled;
private Boolean mIsTouchExplorationEnabled;
private ObserverList<Observer> mObservers;
private final class ModeChangeHandler
implements AccessibilityStateChangeListener, TouchExplorationStateChangeListener {
// AccessibilityStateChangeListener
@Override
public final void onAccessibilityStateChanged(boolean enabled) {
updateIsAccessibilityEnabledAndNotify();
}
// TouchExplorationStateChangeListener
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
updateIsAccessibilityEnabledAndNotify();
}
}
private ModeChangeHandler mModeChangeHandler;
protected AccessibilityUtil() {}
/**
* Checks to see that touch exploration or an accessibility service that can perform gestures
* is enabled.
* @return Whether or not accessibility and touch exploration are enabled.
*/
public boolean isAccessibilityEnabled() {
if (mModeChangeHandler == null) registerModeChangeListeners();
if (mIsAccessibilityEnabled != null) return mIsAccessibilityEnabled;
TraceEvent.begin("AccessibilityManager::isAccessibilityEnabled");
AccessibilityManager manager = getAccessibilityManager();
boolean accessibilityEnabled =
manager != null && manager.isEnabled() && manager.isTouchExplorationEnabled();
mIsTouchExplorationEnabled = accessibilityEnabled;
if (manager != null && manager.isEnabled() && !accessibilityEnabled) {
List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
for (AccessibilityServiceInfo service : services) {
if (canPerformGestures(service)) {
accessibilityEnabled = true;
break;
}
}
}
mIsAccessibilityEnabled = accessibilityEnabled;
TraceEvent.end("AccessibilityManager::isAccessibilityEnabled");
return mIsAccessibilityEnabled;
}
/**
* Get the recommended timeout for changes to the UI needed by this user. The timeout value
* can be set by users on Q+.
*
* https://d.android.com/reference/android/view/accessibility/AccessibilityManager#getRecommendedTimeoutMillis(int,%20int)
* @param originalTimeout The timeout appropriate for users with no accessibility needs.
* @param uiContentFlags The combination of content flags to indicate contents of UI.
* @return The recommended UI timeout for the current user in milliseconds.
*/
@RequiresApi(api = VERSION_CODES.Q)
public int getRecommendedTimeoutMillis(int originalTimeout, int uiContentFlags) {
AccessibilityManager manager = getAccessibilityManager();
assert manager != null : "AccessibilityManager is not available";
return manager.getRecommendedTimeoutMillis(originalTimeout, uiContentFlags);
}
/**
* Add {@link Observer} object. The observer will be notified of the current accessibility
* mode immediately.
* @param observer Observer object monitoring a11y mode change.
*/
@Deprecated
public void addObserver(Observer observer) {
getObservers().addObserver(observer);
// Notify mode change to a new observer so things are initialized correctly when Chrome
// has been re-started after closing due to the last tab being closed when homepage is
// enabled. See crbug.com/541546.
observer.onAccessibilityModeChanged(isAccessibilityEnabled());
observer.onAccessibilityModeChanged(AccessibilityState.isAccessibilityEnabled());
}
/**
* Remove {@link Observer} object.
* @param observer Observer object monitoring a11y mode change.
*/
@Deprecated
public void removeObserver(Observer observer) {
getObservers().removeObserver(observer);
}
private AccessibilityManager getAccessibilityManager() {
return (AccessibilityManager) ContextUtils.getApplicationContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
}
private void registerModeChangeListeners() {
assert mModeChangeHandler == null;
mModeChangeHandler = new ModeChangeHandler();
AccessibilityManager manager = getAccessibilityManager();
manager.addAccessibilityStateChangeListener(mModeChangeHandler);
manager.addTouchExplorationStateChangeListener(mModeChangeHandler);
}
/**
* Removes all global state tracking observers/listeners as well as any observers added to this.
* As this removes all observers, be very careful in calling. In general, only call when the
* application is going to be destroyed.
*/
protected void stopTrackingStateAndRemoveObservers() {
if (mObservers != null) mObservers.clear();
if (mModeChangeHandler == null) return;
AccessibilityManager manager = getAccessibilityManager();
manager.removeAccessibilityStateChangeListener(mModeChangeHandler);
manager.removeTouchExplorationStateChangeListener(mModeChangeHandler);
mModeChangeHandler = null;
}
/**
* Forces recalculating the value of isAccessibilityEnabled(). If the value has changed
* observers are notified.
*/
protected void updateIsAccessibilityEnabledAndNotify() {
boolean oldIsAccessibilityEnabled = isAccessibilityEnabled();
// Setting to null forces the next call to isAccessibilityEnabled() to update the value.
mIsAccessibilityEnabled = null;
mIsTouchExplorationEnabled = null;
if (oldIsAccessibilityEnabled != isAccessibilityEnabled()) notifyModeChange();
@Override
public void onAccessibilityStateChanged(
State oldAccessibilityState, State newAccessibilityState) {
notifyModeChange(AccessibilityState.isAccessibilityEnabled());
}
private ObserverList<Observer> getObservers() {
@ -182,25 +70,12 @@ public class AccessibilityUtil {
/**
* Notify all the observers of the mode change.
*/
private void notifyModeChange() {
boolean enabled = isAccessibilityEnabled();
private void notifyModeChange(boolean isAccessibilityEnabled) {
for (Observer observer : getObservers()) {
observer.onAccessibilityModeChanged(enabled);
observer.onAccessibilityModeChanged(isAccessibilityEnabled);
}
}
/**
* Checks whether the given {@link AccessibilityServiceInfo} can perform gestures.
* @param service The service to check.
* @return Whether the {@code service} can perform gestures. This relies on the capabilities
* the service can perform.
*/
private boolean canPerformGestures(AccessibilityServiceInfo service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES)
!= 0;
}
/**
* Set whether the device has accessibility enabled. Should be reset back to null after the test
* has finished.
@ -208,7 +83,6 @@ public class AccessibilityUtil {
*/
public void setAccessibilityEnabledForTesting(@Nullable Boolean isEnabled) {
ThreadUtils.assertOnUiThread();
mIsAccessibilityEnabled = isEnabled;
notifyModeChange();
notifyModeChange(Boolean.TRUE.equals(isEnabled));
}
}

@ -397,9 +397,7 @@ public class BrowserImpl extends IBrowser.Stub {
mVisibleSecurityStateObservers.clear();
if (--sInstanceCount == 0) {
WebLayerAccessibilityUtil.get().onAllBrowsersDestroyed();
}
--sInstanceCount;
}
void updateAllTabsViewAttachedState() {

@ -5,6 +5,7 @@
package org.chromium.weblayer_private;
import org.chromium.components.browser_ui.accessibility.FontSizePrefs;
import org.chromium.ui.accessibility.AccessibilityState;
import org.chromium.ui.util.AccessibilityUtil;
/**
@ -14,7 +15,10 @@ public class WebLayerAccessibilityUtil extends AccessibilityUtil {
private static WebLayerAccessibilityUtil sInstance;
public static WebLayerAccessibilityUtil get() {
if (sInstance == null) sInstance = new WebLayerAccessibilityUtil();
if (sInstance == null) {
sInstance = new WebLayerAccessibilityUtil();
AccessibilityState.addListener(sInstance);
}
return sInstance;
}
@ -23,13 +27,6 @@ public class WebLayerAccessibilityUtil extends AccessibilityUtil {
public void onBrowserResumed(ProfileImpl profile) {
// When a browser is resumed the cached state may have be stale and needs to be
// recalculated.
updateIsAccessibilityEnabledAndNotify();
FontSizePrefs.getInstance(profile).onSystemFontScaleChanged();
}
public void onAllBrowsersDestroyed() {
// When there are no more browsers alive there is no need to monitor state. Calling
// isAccessibilityEnabled() will trigger observing the necessary state.
stopTrackingStateAndRemoveObservers();
}
}