Commit 34c4aa94 authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Commit Bot

Android: Factor out accessibility handling out of ChromeActivity

This CL factors accessibility-related methods/interfaces out of
ChromeActivity into its own class ChromeAccessibility.
|updateAccessibiilityVisibility| in TabImpl was also moved to
the new class, reducing the ChromeActivity's dependency
on TabImpl.

Bug: 995903
Change-Id: I2296aa25cbcee3e09f1fa48a5ba43ccb9eec92ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2015919
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#740464}
parent 2fe094a1
......@@ -28,9 +28,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
......@@ -92,7 +89,6 @@ import org.chromium.chrome.browser.gsa.GSAAccountChangeListener;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.history.HistoryManagerUtils;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
import org.chromium.chrome.browser.init.StartupTabPreloader;
......@@ -125,6 +121,7 @@ import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.share.ShareDelegateImpl;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.chrome.browser.sync.SyncController;
import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
import org.chromium.chrome.browser.tab.TabHidingType;
......@@ -152,7 +149,6 @@ import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarManageable;
import org.chromium.chrome.browser.ui.system.StatusBarColorController;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.vr.ArDelegate;
import org.chromium.chrome.browser.vr.ArDelegateProvider;
import org.chromium.chrome.browser.vr.VrModuleProvider;
......@@ -201,8 +197,8 @@ import java.util.function.Consumer;
*/
public abstract class ChromeActivity<C extends ChromeActivityComponent>
extends AsyncInitializationActivity
implements TabCreatorManager, AccessibilityStateChangeListener, PolicyChangeListener,
ContextualSearchTabPromotionDelegate, SnackbarManageable, SceneChangeObserver,
implements TabCreatorManager, PolicyChangeListener, ContextualSearchTabPromotionDelegate,
SnackbarManageable, SceneChangeObserver,
StatusBarColorController.StatusBarColorProvider, AppMenuDelegate, AppMenuBlocker,
MenuOrKeyboardActionController {
/**
......@@ -245,12 +241,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
private boolean mTabModelsInitialized;
private boolean mNativeInitialized;
private boolean mRemoveWindowBackgroundDone;
// The class cannot implement TouchExplorationStateChangeListener,
// because it is only available for Build.VERSION_CODES.KITKAT and later.
// We have to instantiate the TouchExplorationStateChangeListner object in the code.
@SuppressLint("NewApi")
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
protected AccessibilityVisibilityHandler mAccessibilityVisibilityHandler;
// Observes when sync becomes ready to create the mContextReporter.
private ProfileSyncService.SyncStateChangedListener mSyncStateChangedListener;
......@@ -422,15 +413,6 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
mAssistStatusHandler.updateAssistState();
}
AccessibilityManager manager = (AccessibilityManager) getBaseContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
manager.addAccessibilityStateChangeListener(this);
mTouchExplorationStateChangeListener = enabled -> {
AccessibilityUtil.resetAccessibilityEnabled();
checkAccessibility();
};
manager.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
// Make the activity listen to policy change events
CombinedPolicyProvider.get().addPolicyChangeListener(this);
mDidAddPolicyChangeListener = true;
......@@ -1128,11 +1110,6 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
}
if (mCompositorViewHolder != null) mCompositorViewHolder.onStart();
// Explicitly call checkAccessibility() 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.
checkAccessibility();
Configuration config = getResources().getConfiguration();
mUiMode = config.uiMode;
mDensityDpi = config.densityDpi;
......@@ -1267,11 +1244,6 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
UpdateMenuItemHelper.getInstance().unregisterObserver(mUpdateStateChangedListener);
AccessibilityManager manager = (AccessibilityManager)
getBaseContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.removeAccessibilityStateChangeListener(this);
manager.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
mActivityTabProvider.destroy();
mComponent = null;
......@@ -1407,23 +1379,6 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
return mNativeInitialized;
}
/**
* Called when the accessibility status of this device changes. This might be triggered by
* touch exploration or general accessibility status updates. It is an aggregate of two other
* accessibility update methods.
*
* @see #onAccessibilityStateChanged
* @see #mTouchExplorationStateChangeListener
* @param enabled Whether or not accessibility and touch exploration are currently enabled.
*/
protected void onAccessibilityModeChanged(boolean enabled) {
InfoBarContainer.setIsAllowedToAutoHide(!enabled);
if (getToolbarManager() != null) getToolbarManager().onAccessibilityStatusChanged(enabled);
if (mContextualSearchManager != null) {
mContextualSearchManager.onAccessibilityModeChanged(enabled);
}
}
@Override
public boolean onOptionsItemSelected(int itemId, @Nullable Bundle menuItemData) {
if (mManualFillingComponent != null) mManualFillingComponent.dismiss();
......@@ -1480,16 +1435,6 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
};
}
@Override
public final void onAccessibilityStateChanged(boolean enabled) {
AccessibilityUtil.resetAccessibilityEnabled();
checkAccessibility();
}
private void checkAccessibility() {
onAccessibilityModeChanged(AccessibilityUtil.isAccessibilityEnabled());
}
/**
* @return A casted version of {@link #getApplication()}.
*/
......
......@@ -170,7 +170,7 @@ import java.util.Locale;
* This is the main activity for ChromeMobile when not running in document mode. All the tabs
* are accessible via a chrome specific tab switching UI.
*/
public class ChromeTabbedActivity extends ChromeActivity {
public class ChromeTabbedActivity extends ChromeActivity implements AccessibilityUtil.Observer {
private static final String TAG = "ChromeTabbedActivity";
private static final String HELP_URL_PREFIX = "https://support.google.com/chrome/";
......@@ -826,6 +826,7 @@ public class ChromeTabbedActivity extends ChromeActivity {
this::maybeGetFeedAppLifecycleAndMaybeCreatePageViewObserver);
PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::addOverviewModeObserver);
PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::finishNativeInitialization);
AccessibilityUtil.addObserver(this);
}
}
......@@ -838,6 +839,9 @@ public class ChromeTabbedActivity extends ChromeActivity {
mOverviewListLayout = (OverviewListLayout) mLayoutManager.getOverviewListLayout();
getTabObscuringHandler().addObserver(mCompositorViewHolder);
getTabObscuringHandler().addObserver(mOverviewListLayout);
AccessibilityUtil.addObserver(mLayoutManager);
if (isTablet()) AccessibilityUtil.addObserver(mCompositorViewHolder);
}
@Override
......@@ -1169,17 +1173,6 @@ public class ChromeTabbedActivity extends ChromeActivity {
@Override
public void onAccessibilityModeChanged(boolean enabled) {
super.onAccessibilityModeChanged(enabled);
if (mLayoutManager != null) {
mLayoutManager.setEnableAnimations(DeviceClassManager.enableAnimations());
}
if (isTablet()) {
if (getCompositorViewHolder() != null) {
getCompositorViewHolder().onAccessibilityStatusChanged(enabled);
}
}
onAccessibilityTabSwitcherModeChanged();
}
......@@ -2024,6 +2017,10 @@ public class ChromeTabbedActivity extends ChromeActivity {
getTabObscuringHandler().removeObserver(mOverviewListLayout);
}
if (isTablet()) AccessibilityUtil.removeObserver(mCompositorViewHolder);
AccessibilityUtil.removeObserver(this);
AccessibilityUtil.removeObserver(mLayoutManager);
super.onDestroyInternal();
}
......
......@@ -59,6 +59,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ControlContainer;
import org.chromium.chrome.browser.toolbar.ToolbarColors;
import org.chromium.chrome.browser.ui.TabObscuringHandler;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.components.content_capture.ContentCaptureConsumer;
import org.chromium.components.content_capture.ContentCaptureConsumerImpl;
......@@ -86,7 +87,8 @@ import java.util.Set;
public class CompositorViewHolder extends FrameLayout
implements ContentOffsetProvider, LayoutManagerHost, LayoutRenderHost, Invalidator.Host,
FullscreenListener, InsetObserverView.WindowInsetObserver,
CompositorViewResizer.Observer, TabObscuringHandler.Observer {
CompositorViewResizer.Observer, AccessibilityUtil.Observer,
TabObscuringHandler.Observer {
private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500;
/**
......@@ -1300,14 +1302,14 @@ public class CompositorViewHolder extends FrameLayout
}
}
/**
* Called when the accessibility enabled state changes.
* @param enabled Whether accessibility is enabled.
*/
public void onAccessibilityStatusChanged(boolean enabled) {
// AccessibilityUtil.Observer
@Override
public void onAccessibilityModeChanged(boolean enabled) {
// Instantiate and install the accessibility node provider on this view if necessary.
// This overrides any hover event listeners or accessibility delegates
// that may have been added elsewhere.
assert mLayoutManager != null;
if (enabled && (mNodeProvider == null)) {
mAccessibilityView = new View(getContext());
addView(mAccessibilityView);
......
......@@ -45,7 +45,8 @@ import java.util.List;
* A {@link Layout} controller for the more complicated Chrome browser. This is currently a
* superset of {@link LayoutManager}.
*/
public class LayoutManagerChrome extends LayoutManager implements OverviewModeController {
public class LayoutManagerChrome
extends LayoutManager implements OverviewModeController, AccessibilityUtil.Observer {
// Layouts
/** An {@link Layout} that should be used as the accessibility tab switcher. */
protected OverviewListLayout mOverviewListLayout;
......@@ -414,6 +415,13 @@ public class LayoutManagerChrome extends LayoutManager implements OverviewModeCo
mOverviewModeObservers.removeObserver(listener);
}
// AccessibilityUtil.Observer
@Override
public void onAccessibilityModeChanged(boolean enabled) {
setEnableAnimations(DeviceClassManager.enableAnimations());
}
/**
* A {@link EdgeSwipeHandler} meant to respond to edge events for the toolbar.
*/
......
......@@ -53,6 +53,7 @@ import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
......@@ -77,9 +78,9 @@ import java.util.regex.Pattern;
* Manager for the Contextual Search feature. This class keeps track of the status of Contextual
* Search and coordinates the control with the layout.
*/
public class ContextualSearchManager implements ContextualSearchManagementDelegate,
ContextualSearchNetworkCommunicator,
ContextualSearchSelectionHandler {
public class ContextualSearchManager
implements ContextualSearchManagementDelegate, ContextualSearchNetworkCommunicator,
ContextualSearchSelectionHandler, AccessibilityUtil.Observer {
/** A delegate for reporting selected context to GSA for search quality. */
public interface ContextReporterDelegate {
/**
......@@ -287,6 +288,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
mInternalStateController.reset(StateChangeReason.UNKNOWN);
listenForTabModelSelectorNotifications();
AccessibilityUtil.addObserver(this);
}
/**
......@@ -303,6 +305,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
stopListeningForHideNotifications();
mTabRedirectHandler.clear();
mInternalStateController.enter(InternalState.UNDEFINED);
AccessibilityUtil.removeObserver(this);
}
@Override
......@@ -879,11 +882,7 @@ public class ContextualSearchManager implements ContextualSearchManagementDelega
}
}
/**
* Notifies that the Accessibility Mode state has changed.
*
* @param enabled Whether the Accessibility Mode is enabled.
*/
@Override
public void onAccessibilityModeChanged(boolean enabled) {
mIsAccessibilityModeEnabled = enabled;
if (enabled) hideContextualSearch(StateChangeReason.UNKNOWN);
......
......@@ -22,6 +22,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
......@@ -42,6 +43,13 @@ public class InfoBarContainer implements UserData, KeyboardVisibilityListener {
private static final Class<InfoBarContainer> USER_DATA_KEY = InfoBarContainer.class;
private static final AccessibilityUtil.Observer sAccessibilityObserver;
static {
sAccessibilityObserver = (enabled) -> setIsAllowedToAutoHide(!enabled);
AccessibilityUtil.addObserver(sAccessibilityObserver);
}
/**
* A listener for the InfoBar animations.
*/
......@@ -426,7 +434,7 @@ public class InfoBarContainer implements UserData, KeyboardVisibilityListener {
* Expected to be called when Touch Exploration is enabled.
* @param isAllowed Whether auto-hiding is allowed.
*/
public static void setIsAllowedToAutoHide(boolean isAllowed) {
private static void setIsAllowedToAutoHide(boolean isAllowed) {
InfoBarContainerView.setIsAllowedToAutoHide(isAllowed);
}
......
......@@ -109,6 +109,7 @@ import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.MenuButtonDelegate;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.browser.widget.ScrimView.ScrimObserver;
......@@ -137,7 +138,8 @@ import java.util.List;
* with the rest of the application to ensure the toolbar is always visually up to date.
*/
public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlFocusChangeListener,
ThemeColorObserver, MenuButtonDelegate {
ThemeColorObserver, MenuButtonDelegate,
AccessibilityUtil.Observer {
/**
* Handle UI updates of menu icons. Only applicable for phones.
*/
......@@ -753,6 +755,8 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
mToolbar.setIncognitoStateProvider(mIncognitoStateProvider);
mToolbar.setThemeColorProvider(
mActivity.isTablet() ? mAppThemeColorProvider : mTabThemeColorProvider);
AccessibilityUtil.addObserver(this);
}
/**
......@@ -1214,6 +1218,7 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
if (mAppThemeColorProvider != null) mAppThemeColorProvider.destroy();
mActivity.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
AccessibilityUtil.removeObserver(this);
}
/**
......@@ -1245,11 +1250,8 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
}
}
/**
* Called when the accessibility enabled state changes.
* @param enabled Whether accessibility is enabled.
*/
public void onAccessibilityStatusChanged(boolean enabled) {
@Override
public void onAccessibilityModeChanged(boolean enabled) {
mToolbar.onAccessibilityStatusChanged(enabled);
}
......
......@@ -10,6 +10,8 @@ import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
......@@ -18,6 +20,7 @@ import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.TraceEvent;
import java.util.List;
......@@ -26,8 +29,41 @@ import java.util.List;
* Exposes information about the current accessibility state
*/
public class AccessibilityUtil {
/**
* An observer to be notified of accessibility status changes.
*/
public interface Observer {
/**
* @param enabled Whether a touch exploration or an accessibility service that performs can
* perform gestures is enabled. Indicates that the UI must be fully navigable using
* the accessibility view tree.
*/
void onAccessibilityModeChanged(boolean enabled);
}
private static Boolean sIsAccessibilityEnabled;
private static ActivityStateListener sActivityStateListener;
private static ObserverList<Observer> sObservers;
private static class ModeChangeHandler
implements AccessibilityStateChangeListener, TouchExplorationStateChangeListener {
// AccessibilityStateChangeListener
@Override
public final void onAccessibilityStateChanged(boolean enabled) {
resetAccessibilityEnabled();
notifyModeChange();
}
// TouchExplorationStateChangeListener
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
resetAccessibilityEnabled();
notifyModeChange();
}
}
private static ModeChangeHandler sModeChangeHandler;
private AccessibilityUtil() {}
......@@ -36,13 +72,12 @@ public class AccessibilityUtil {
* @return Whether or not accessibility and touch exploration are enabled.
*/
public static boolean isAccessibilityEnabled() {
if (sModeChangeHandler == null) registerModeChangeListeners();
if (sIsAccessibilityEnabled != null) return sIsAccessibilityEnabled;
TraceEvent.begin("AccessibilityManager::isAccessibilityEnabled");
AccessibilityManager manager =
(AccessibilityManager) ContextUtils.getApplicationContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
AccessibilityManager manager = getAccessibilityManager();
boolean accessibilityEnabled =
manager != null && manager.isEnabled() && manager.isTouchExplorationEnabled();
......@@ -72,6 +107,7 @@ public class AccessibilityUtil {
|| ApplicationStatus.isEveryActivityDestroyed()) {
resetAccessibilityEnabled();
}
if (ApplicationStatus.isEveryActivityDestroyed()) cleanUp();
}
};
ApplicationStatus.registerStateListenerForAllActivities(sActivityStateListener);
......@@ -80,23 +116,78 @@ public class AccessibilityUtil {
return sIsAccessibilityEnabled;
}
/**
* Add {@link Observer} object. The observer will be notified of the current accessibility
* mode immediately.
* @param observer Observer object monitoring a11y mode change.
*/
public static 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());
}
/**
* Remove {@link Observer} object.
* @param observer Observer object monitoring a11y mode change.
*/
public static void removeObserver(Observer observer) {
getObservers().removeObserver(observer);
}
/**
* @return True if a hardware keyboard is detected.
*/
public static boolean isHardwareKeyboardAttached(Configuration c) {
return c.keyboard != Configuration.KEYBOARD_NOKEYS;
}
private static AccessibilityManager getAccessibilityManager() {
return (AccessibilityManager) ContextUtils.getApplicationContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
}
private static void registerModeChangeListeners() {
assert sModeChangeHandler == null;
sModeChangeHandler = new ModeChangeHandler();
AccessibilityManager manager = getAccessibilityManager();
manager.addAccessibilityStateChangeListener(sModeChangeHandler);
manager.addTouchExplorationStateChangeListener(sModeChangeHandler);
}
private static void cleanUp() {
if (sObservers != null) sObservers.clear();
if (sModeChangeHandler == null) return;
AccessibilityManager manager = getAccessibilityManager();
manager.removeAccessibilityStateChangeListener(sModeChangeHandler);
manager.removeTouchExplorationStateChangeListener(sModeChangeHandler);
}
/**
* Reset the static used to determine whether accessibility is enabled.
* TODO(twellington): Make this private and have classes that care about accessibility state
* observe this class rather than observing the AccessibilityManager
* directly.
*/
public static void resetAccessibilityEnabled() {
private static void resetAccessibilityEnabled() {
ApplicationStatus.unregisterActivityStateListener(sActivityStateListener);
sActivityStateListener = null;
sIsAccessibilityEnabled = null;
}
private static ObserverList<Observer> getObservers() {
if (sObservers == null) sObservers = new ObserverList<>();
return sObservers;
}
/**
* @return True if a hardware keyboard is detected.
* Notify all the observers of the mode change.
*/
public static boolean isHardwareKeyboardAttached(Configuration c) {
return c.keyboard != Configuration.KEYBOARD_NOKEYS;
private static void notifyModeChange() {
boolean enabled = isAccessibilityEnabled();
for (Observer observer : getObservers()) {
observer.onAccessibilityModeChanged(enabled);
}
}
/**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment