diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java new file mode 100644 index 0000000000000..39d8dbb3beec0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; + +import android.annotation.IntDef; +import android.annotation.MainThread; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.SysUISingleton; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.inject.Inject; + +/** + * Observes changes of the accessibility button mode + * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE} and notify its listeners. + */ +@MainThread +@SysUISingleton +public class AccessibilityButtonModeObserver { + + private static final int ACCESSIBILITY_BUTTON_MODE_DEFAULT = + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + private final List mListeners = new ArrayList<>(); + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU + }) + private @interface AccessibilityButtonMode {} + + /** Listener for accessibility button mode changes. */ + public interface ModeChangedListener { + + /** + * Called when accessibility button mode changes. + * + * @param mode Current accessibility button mode. + */ + void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode); + } + + @Inject + public AccessibilityButtonModeObserver(Context context) { + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateAccessibilityButtonModeChanged(); + } + }; + } + + /** + * Registers a listener to receive updates from settings key {@link + * Settings.Secure#ACCESSIBILITY_BUTTON_MODE}. + * + * @param listener A {@link ModeChangedListener} object. + */ + public void addListener(@NonNull ModeChangedListener listener) { + Objects.requireNonNull(listener, "listener must be non-null"); + + mListeners.add(listener); + + if (mListeners.size() == 1) { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */ + false, mContentObserver); + } + } + + /** + * Unregisters a listener previously registered with {@link #addListener(ModeChangedListener)}. + * + * @param listener A {@link ModeChangedListener} object. + */ + public void removeListener(@NonNull ModeChangedListener listener) { + Objects.requireNonNull(listener, "listener must be non-null"); + + mListeners.remove(listener); + + if (mListeners.isEmpty()) { + mContentResolver.unregisterContentObserver(mContentObserver); + } + } + + /** + * Gets the current accessibility button mode from the current user's settings. + * + * See {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE}. + */ + @AccessibilityButtonMode + public int getCurrentAccessibilityButtonMode() { + return Settings.Secure.getInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_DEFAULT); + } + + private void updateAccessibilityButtonModeChanged() { + final int mode = getCurrentAccessibilityButtonMode(); + final int listenerSize = mListeners.size(); + for (int i = 0; i < listenerSize; i++) { + mListeners.get(i).onAccessibilityButtonModeChanged(mode); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index a2f96bbad2035..fbcc12a92c8a6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -45,6 +45,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.ModeSwitchesController; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; @@ -212,6 +213,7 @@ public class DependencyProvider { MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, @@ -236,6 +238,7 @@ public class DependencyProvider { metricsLogger, overviewProxyService, navigationModeController, + accessibilityButtonModeObserver, statusBarStateController, sysUiFlagsContainer, broadcastDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 553623702aedb..b4616ac809112 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -111,6 +111,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistHandleViewController; import com.android.systemui.assist.AssistManager; @@ -156,7 +157,8 @@ import dagger.Lazy; * Contains logic for a navigation bar view. */ public class NavigationBar implements View.OnAttachStateChangeListener, - Callbacks, NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener { + Callbacks, NavigationModeController.ModeChangedListener, + AccessibilityButtonModeObserver.ModeChangedListener, DisplayManager.DisplayListener { public static final String TAG = "NavigationBar"; private static final boolean DEBUG = false; @@ -184,6 +186,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final OverviewProxyService mOverviewProxyService; private final NavigationModeController mNavigationModeController; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; private final BroadcastDispatcher mBroadcastDispatcher; private final CommandQueue mCommandQueue; private final Optional mPipOptional; @@ -222,6 +225,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private boolean mTransientShown; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; + private int mA11yBtnMode; private LightBarController mLightBarController; private AutoHideController mAutoHideController; @@ -414,6 +418,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, @@ -441,7 +446,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNotificationRemoteInputManager = notificationRemoteInputManager; mOverviewProxyService = overviewProxyService; mNavigationModeController = navigationModeController; - mNavBarMode = navigationModeController.addListener(this); + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; mPipOptional = pipOptional; @@ -451,6 +456,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; + + mNavBarMode = mNavigationModeController.addListener(this); + mAccessibilityButtonModeObserver.addListener(this); + mA11yBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); } public View getView() { @@ -524,6 +533,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mContext.getSystemService(WindowManager.class).removeViewImmediate( mNavigationBarView.getRootView()); mNavigationModeController.removeListener(this); + mAccessibilityButtonModeObserver.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mAssistContentObserver); @@ -1391,6 +1401,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, outFeedbackEnabled[0] = feedbackEnabled; } + // If accessibility button is floating menu mode, click and long click state should be + // disabled. + if (mA11yBtnMode == Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return 0; + } + return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); } @@ -1475,6 +1491,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } + @Override + public void onAccessibilityButtonModeChanged(int mode) { + mA11yBtnMode = mode; + updateAccessibilityServicesState(mAccessibilityManager); + } + public void disableAnimationsDuringHide(long delay) { mNavigationBarView.setLayoutTransitionsEnabled(false); mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true), diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 4491cc12a3cb9..166de44076a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -43,6 +43,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.Dumpable; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistHandleViewController; import com.android.systemui.assist.AssistManager; @@ -91,6 +92,7 @@ public class NavigationBarController implements Callbacks, private final MetricsLogger mMetricsLogger; private final OverviewProxyService mOverviewProxyService; private final NavigationModeController mNavigationModeController; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; private final StatusBarStateController mStatusBarStateController; private final SysUiState mSysUiFlagsContainer; private final BroadcastDispatcher mBroadcastDispatcher; @@ -127,6 +129,7 @@ public class NavigationBarController implements Callbacks, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, @@ -151,6 +154,7 @@ public class NavigationBarController implements Callbacks, mMetricsLogger = metricsLogger; mOverviewProxyService = overviewProxyService; mNavigationModeController = navigationModeController; + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; mStatusBarStateController = statusBarStateController; mSysUiFlagsContainer = sysUiFlagsContainer; mBroadcastDispatcher = broadcastDispatcher; @@ -281,6 +285,7 @@ public class NavigationBarController implements Callbacks, mMetricsLogger, mOverviewProxyService, mNavigationModeController, + mAccessibilityButtonModeObserver, mStatusBarStateController, mSysUiFlagsContainer, mBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java new file mode 100644 index 0000000000000..0eab0bb167b08 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class AccessibilityButtonModeObserverTest extends SysuiTestCase { + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AccessibilityButtonModeObserver.ModeChangedListener mListener; + + private AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + + private static final int TEST_A11Y_BTN_MODE_VALUE = + Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + + @Before + public void setUpController() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext); + } + + @Test + public void onChange_haveListener_invokeCallback() { + mAccessibilityButtonModeObserver.addListener(mListener); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE); + + mAccessibilityButtonModeObserver.mContentObserver.onChange(false); + + verify(mListener).onAccessibilityButtonModeChanged(TEST_A11Y_BTN_MODE_VALUE); + } + + @Test + public void onChange_noListener_noInvokeCallback() { + mAccessibilityButtonModeObserver.addListener(mListener); + mAccessibilityButtonModeObserver.removeListener(mListener); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE); + + mAccessibilityButtonModeObserver.mContentObserver.onChange(false); + + verify(mListener, never()).onAccessibilityButtonModeChanged(anyInt()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index e761da4c4675c..c29b812dde7a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -45,6 +45,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -93,6 +94,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(MetricsLogger.class), mock(OverviewProxyService.class), mock(NavigationModeController.class), + mock(AccessibilityButtonModeObserver.class), mock(StatusBarStateController.class), mock(SysUiState.class), mock(BroadcastDispatcher.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 2b76f1c2d76d8..06f1bb563cc27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -61,6 +61,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -220,6 +221,7 @@ public class NavigationBarTest extends SysuiTestCase { new MetricsLogger(), mOverviewProxyService, mock(NavigationModeController.class), + mock(AccessibilityButtonModeObserver.class), mock(StatusBarStateController.class), mMockSysUiState, mBroadcastDispatcher,