diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index d10ff402aa3ae..5d4078d3419b4 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -252,4 +252,8 @@ true + + + 1 + diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 400742ba7d78a..081f3f6737021 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3399,7 +3399,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 198; + private static final int SETTINGS_VERSION = 199; private final int mUserId; @@ -4897,6 +4897,36 @@ public class SettingsProvider extends ContentProvider { currentVersion = 198; } + if (currentVersion == 198) { + // Version 198: Set the default value for accessibility button. If the user + // uses accessibility button in the navigation bar to trigger their + // accessibility features (check if ACCESSIBILITY_BUTTON_TARGETS has value) + // then leave accessibility button mode in the navigation bar, otherwise, set it + // to the floating menu. + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting accessibilityButtonMode = secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_BUTTON_MODE); + if (accessibilityButtonMode.isNull()) { + if (isAccessibilityButtonInNavigationBarOn(secureSettings)) { + secureSettings.insertSettingLocked(Secure.ACCESSIBILITY_BUTTON_MODE, + String.valueOf( + Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR), + /*tag= */ null, /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } else { + final int defAccessibilityButtonMode = + getContext().getResources().getInteger( + R.integer.def_accessibility_button_mode); + secureSettings.insertSettingLocked(Secure.ACCESSIBILITY_BUTTON_MODE, + String.valueOf(defAccessibilityButtonMode), /* tag= */ + null, /* makeDefault= */ true, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 199; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { @@ -5075,5 +5105,15 @@ public class SettingsProvider extends ContentProvider { } return items; } + + private boolean isAccessibilityButtonInNavigationBarOn(SettingsState secureSettings) { + final boolean hasValueInA11yBtnTargets = !TextUtils.isEmpty( + secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_BUTTON_TARGETS).getValue()); + final int navigationMode = getContext().getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + + return hasValueInA11yBtnTargets && (navigationMode != NAV_BAR_MODE_GESTURAL); + } } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 78180a7a80a85..685896523e361 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2673,6 +2673,12 @@ Switch + + + Accessibility button replaced the accessibility gesture\n\nView settings + + Move button to the edge to hide it temporarily + Device controls diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index ae4c8e5a33279..06b486ec43d0b 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -34,6 +34,9 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.clock.ClockManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -284,6 +287,8 @@ public class Dependency { @Inject Lazy mIWindowManager; @Inject Lazy mOverviewProxyService; @Inject Lazy mNavBarModeController; + @Inject Lazy mAccessibilityButtonModeObserver; + @Inject Lazy mAccessibilityButtonListController; @Inject Lazy mEnhancedEstimates; @Inject Lazy mVibratorHelper; @Inject Lazy mIStatusBarService; @@ -294,6 +299,7 @@ public class Dependency { @Inject Lazy mNotificationRemoteInputManagerCallback; @Inject Lazy mAppOpsController; @Inject Lazy mNavigationBarController; + @Inject Lazy mAccessibilityFloatingMenuController; @Inject Lazy mStatusBarStateController; @Inject Lazy mNotificationLockscreenUserManager; @Inject Lazy mNotificationGroupAlertTransferHelper; @@ -470,6 +476,11 @@ public class Dependency { mProviders.put(NavigationModeController.class, mNavBarModeController::get); + mProviders.put(AccessibilityButtonModeObserver.class, + mAccessibilityButtonModeObserver::get); + mProviders.put(AccessibilityButtonTargetsObserver.class, + mAccessibilityButtonListController::get); + mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); mProviders.put(VibratorHelper.class, mVibratorHelper::get); @@ -489,6 +500,9 @@ public class Dependency { mProviders.put(NavigationBarController.class, mNavigationBarController::get); + mProviders.put(AccessibilityFloatingMenuController.class, + mAccessibilityFloatingMenuController::get); + mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); mProviders.put(NotificationLockscreenUserManager.class, mNotificationLockscreenUserManager::get); 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..9bedb1ea75637 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java @@ -0,0 +1,101 @@ +/* + * 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.Context; +import android.provider.Settings; +import android.util.Log; + +import com.android.systemui.dagger.SysUISingleton; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +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 extends + SecureSettingsContentObserver { + + private static final String TAG = "A11yButtonModeObserver"; + + private static final int ACCESSIBILITY_BUTTON_MODE_DEFAULT = + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU + }) + public @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) { + super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE); + } + + @Override + void onValueChanged(ModeChangedListener listener, String value) { + final int mode = parseAccessibilityButtonMode(value); + listener.onAccessibilityButtonModeChanged(mode); + } + + /** + * Gets the current accessibility button mode from the current user's settings. + * + * See {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE}. + */ + public int getCurrentAccessibilityButtonMode() { + final String value = getSettingsValue(); + + return parseAccessibilityButtonMode(value); + } + + private int parseAccessibilityButtonMode(String value) { + int mode; + + try { + mode = Integer.parseInt(value); + } catch (NumberFormatException e) { + Log.e(TAG, "Invalid string for " + e); + mode = ACCESSIBILITY_BUTTON_MODE_DEFAULT; + } + + return mode; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java new file mode 100644 index 0000000000000..b32ebccdd7dbb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java @@ -0,0 +1,66 @@ +/* + * 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 android.content.Context; +import android.provider.Settings; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; + +import com.android.systemui.dagger.SysUISingleton; + +import javax.inject.Inject; + +/** + * Controller for tracking the current accessibility button list. + * + * @see Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS + */ +@MainThread +@SysUISingleton +public class AccessibilityButtonTargetsObserver extends + SecureSettingsContentObserver { + + /** Listener for accessibility button targets changes. */ + public interface TargetsChangedListener { + + /** + * Called when accessibility button targets changes. + * + * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS} + */ + void onAccessibilityButtonTargetsChanged(String targets); + } + + @Inject + public AccessibilityButtonTargetsObserver(Context context) { + super(context, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + } + + @Override + void onValueChanged(TargetsChangedListener listener, String value) { + listener.onAccessibilityButtonTargetsChanged(value); + } + + /** Returns the current string from settings key + * {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}. */ + @Nullable + public String getCurrentAccessibilityButtonTargets() { + return getSettingsValue(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java new file mode 100644 index 0000000000000..4f8d8666a24ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java @@ -0,0 +1,121 @@ +/* + * 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 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 java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Provides basic methods for adding, removing arbitrary listeners and inquiry given {@code + * secureSettingsKey} value; it must comes from {@link Settings.Secure}. + * + * This abstract class is intended to be subclassed and specialized to maintain + * a registry of listeners of specific types and dispatch changes to them. + * + * @param The listener type + */ +public abstract class SecureSettingsContentObserver { + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + private final String mKey; + + @VisibleForTesting + final List mListeners = new ArrayList<>(); + + protected SecureSettingsContentObserver(Context context, String secureSettingsKey) { + mKey = secureSettingsKey; + mContentResolver = context.getContentResolver(); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateValueChanged(); + } + }; + } + + /** + * Registers a listener to receive updates from given settings key {@code secureSettingsKey}. + * + * @param listener A listener to be added to receive the changes + */ + public void addListener(@NonNull T listener) { + Objects.requireNonNull(listener, "listener must be non-null"); + + mListeners.add(listener); + + if (mListeners.size() == 1) { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(mKey), /* notifyForDescendants= */ + false, mContentObserver); + } + } + + /** + * Unregisters a listener previously registered with {@link #addListener(T listener)}. + * + * @param listener A listener to be removed from receiving the changes + */ + public void removeListener(@NonNull T listener) { + Objects.requireNonNull(listener, "listener must be non-null"); + + mListeners.remove(listener); + + if (mListeners.isEmpty()) { + mContentResolver.unregisterContentObserver(mContentObserver); + } + } + + /** + * Gets the value from the current user's secure settings. + * + * See {@link Settings.Secure}. + */ + public final String getSettingsValue() { + return Settings.Secure.getString(mContentResolver, mKey); + } + + private void updateValueChanged() { + final String value = getSettingsValue(); + final int listenerSize = mListeners.size(); + for (int i = 0; i < listenerSize; i++) { + onValueChanged(mListeners.get(i), value); + } + } + + /** + * Called when the registered value from {@code secureSettingsKey} changes. + * + * @param listener A listener could be used to receive the updates + * @param value Content changed value from {@code secureSettingsKey} + */ + abstract void onValueChanged(T listener, String value); +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java new file mode 100644 index 0000000000000..112e9cae0a214 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java @@ -0,0 +1,145 @@ +/* + * 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.floatingmenu; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + +import android.content.Context; +import android.text.TextUtils; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.MainThread; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode; +import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.dagger.SysUISingleton; + +import javax.inject.Inject; + +/** A controller to handle the lifecycle of accessibility floating menu. */ +@MainThread +@SysUISingleton +public class AccessibilityFloatingMenuController implements + AccessibilityButtonModeObserver.ModeChangedListener, + AccessibilityButtonTargetsObserver.TargetsChangedListener, + AccessibilityManager.AccessibilityStateChangeListener { + + private final Context mContext; + private final AccessibilityManager mAccessibilityManager; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver; + + @VisibleForTesting + IAccessibilityFloatingMenu mFloatingMenu; + private int mBtnMode; + private String mBtnTargets; + + @Inject + public AccessibilityFloatingMenuController(Context context, + AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, + AccessibilityButtonModeObserver accessibilityButtonModeObserver) { + mContext = context; + mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver; + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; + mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); + + mAccessibilityButtonModeObserver.addListener(this); + mAccessibilityButtonTargetsObserver.addListener(this); + mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); + mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets(); + + // Accessibility floating menu widget needs accessibility service to work, but system + // accessibility might be unavailable during the phone get booted, hence it needs to wait + // for accessibility manager callback to work. + mAccessibilityManager.addAccessibilityStateChangeListener(this); + if (mAccessibilityManager.isEnabled()) { + handleFloatingMenuVisibility(mBtnMode, mBtnTargets); + mAccessibilityManager.removeAccessibilityStateChangeListener(this); + } + } + + /** + * Handles visibility of the accessibility floating menu when accessibility button mode changes. + * + * @param mode Current accessibility button mode. + */ + @Override + public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) { + mBtnMode = mode; + handleFloatingMenuVisibility(mBtnMode, mBtnTargets); + } + + /** + * Handles visibility of the accessibility floating menu when accessibility button targets + * changes. + * List should come from {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}. + * @param targets Current accessibility button list. + */ + @Override + public void onAccessibilityButtonTargetsChanged(String targets) { + mBtnTargets = targets; + handleFloatingMenuVisibility(mBtnMode, mBtnTargets); + } + + /** + * Handles visibility of the accessibility floating menu when system accessibility state + * changes. + * If system accessibility become available onAccessibilityStateChanged(true), then we don't + * need to listen to this listener anymore. + * + * @param enabled Whether accessibility is enabled. + */ + @Override + public void onAccessibilityStateChanged(boolean enabled) { + if (enabled) { + handleFloatingMenuVisibility(mBtnMode, mBtnTargets); + } + + mAccessibilityManager.removeAccessibilityStateChangeListener(this); + } + + private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) { + if (shouldShowFloatingMenu(mode, targets)) { + showFloatingMenu(); + } else { + destroyFloatingMenu(); + } + } + + private boolean shouldShowFloatingMenu(@AccessibilityButtonMode int mode, String targets) { + return mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU && !TextUtils.isEmpty(targets); + } + + private void showFloatingMenu() { + if (mFloatingMenu == null) { + mFloatingMenu = new AccessibilityFloatingMenu(mContext); + } + + mFloatingMenu.show(); + } + + private void destroyFloatingMenu() { + if (mFloatingMenu == null) { + return; + } + + mFloatingMenu.hide(); + mFloatingMenu = null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 8e344d2c2df74..1a729295165c4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -46,8 +46,11 @@ 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.AccessibilityButtonTargetsObserver; import com.android.systemui.accessibility.ModeSwitchesController; import com.android.systemui.accessibility.SystemActions; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; @@ -213,6 +216,7 @@ public class DependencyProvider { MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, @@ -237,6 +241,7 @@ public class DependencyProvider { metricsLogger, overviewProxyService, navigationModeController, + accessibilityButtonModeObserver, statusBarStateController, sysUiFlagsContainer, broadcastDispatcher, @@ -254,6 +259,16 @@ public class DependencyProvider { configurationController); } + /** */ + @Provides + @SysUISingleton + public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController( + Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, + AccessibilityButtonModeObserver accessibilityButtonModeObserver) { + return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver, + accessibilityButtonModeObserver); + } + /** */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index a0906dfdfd5e4..9d43e0c133200 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -23,6 +23,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.containsType; @@ -113,6 +114,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; @@ -158,7 +160,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; @@ -186,6 +189,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; @@ -226,6 +230,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; @@ -443,6 +448,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, @@ -470,7 +476,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; @@ -480,6 +486,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() { @@ -552,6 +562,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup(); mDeviceProvisionedController.addCallback(mUserSetupListener); + setAccessibilityFloatingMenuModeIfNeeded(); + return barView; } @@ -560,6 +572,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); @@ -1395,6 +1408,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, updateSystemUiStateFlags(a11yFlags); } + private void setAccessibilityFloatingMenuModeIfNeeded() { + if (QuickStepContract.isGesturalMode(mNavBarMode)) { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + } + } + public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { a11yFlags = getA11yButtonState(null); @@ -1450,6 +1470,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 == 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); } @@ -1534,12 +1560,19 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } updateScreenPinningGestures(); + setAccessibilityFloatingMenuModeIfNeeded(); if (!canShowSecondaryHandle()) { resetSecondaryHandle(); } } + @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 ca69e6de2d0a9..50efa8def5bfc 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; @@ -289,6 +293,7 @@ public class NavigationBarController implements Callbacks, mMetricsLogger, mOverviewProxyService, mNavigationModeController, + mAccessibilityButtonModeObserver, mStatusBarStateController, mSysUiFlagsContainer, mBroadcastDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c6ef8a3d7ba4b..6dd00a5ec70d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -144,6 +144,7 @@ import com.android.systemui.InitController; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SystemUI; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.charging.WirelessChargingAnimation; @@ -737,6 +738,7 @@ public class StatusBar extends SystemUI implements DemoMode, VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, + AccessibilityFloatingMenuController accessibilityFloatingMenuController, Lazy assistManagerLazy, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index b572c57590ae5..17bb449ec5f49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -29,6 +29,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -164,6 +165,7 @@ public interface StatusBarPhoneModule { VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, + AccessibilityFloatingMenuController accessibilityFloatingMenuController, Lazy assistManagerLazy, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, @@ -245,6 +247,7 @@ public interface StatusBarPhoneModule { visualStabilityManager, deviceProvisionedController, navigationBarController, + accessibilityFloatingMenuController, assistManagerLazy, configurationController, notificationShadeWindowController, 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..01b7adefbacf5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java @@ -0,0 +1,97 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +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 setUp() { + 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()); + } + + @Test + public void getCurrentAccessibilityButtonMode_expectedValue() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE); + + final int actualValue = + mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); + + assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_MODE_VALUE); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java new file mode 100644 index 0000000000000..1e49fc998ea4f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java @@ -0,0 +1,93 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +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; + +/** Test for {@link AccessibilityButtonTargetsObserver}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class AccessibilityButtonTargetsObserverTest extends SysuiTestCase { + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private AccessibilityButtonTargetsObserver.TargetsChangedListener mListener; + + private AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver; + + private static final String TEST_A11Y_BTN_TARGETS = "Magnification"; + + @Before + public void setUp() { + mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext); + } + + @Test + public void onChange_haveListener_invokeCallback() { + mAccessibilityButtonTargetsObserver.addListener(mListener); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + + mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false); + + verify(mListener).onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); + } + + @Test + public void onChange_listenerRemoved_noInvokeCallback() { + mAccessibilityButtonTargetsObserver.addListener(mListener); + mAccessibilityButtonTargetsObserver.removeListener(mListener); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + + mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false); + + verify(mListener, never()).onAccessibilityButtonTargetsChanged(anyString()); + } + + @Test + public void getCurrentAccessibilityButtonTargets_expectedValue() { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + + final String actualValue = + mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets(); + + assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_TARGETS); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java new file mode 100644 index 0000000000000..5b1c441a71a1c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java @@ -0,0 +1,77 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import android.content.Context; +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.Test; +import org.junit.runner.RunWith; + +/** Test for {@link SecureSettingsContentObserver}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SecureSettingsContentObserverTest extends SysuiTestCase { + + private FakeSecureSettingsContentObserver mTestObserver; + + @Before + public void setUpObserver() { + mTestObserver = new FakeSecureSettingsContentObserver(mContext, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE); + } + + @Test(expected = NullPointerException.class) + public void addNullListener_throwNPE() { + mTestObserver.addListener(null); + } + + @Test(expected = NullPointerException.class) + public void removeNullListener_throwNPE() { + mTestObserver.removeListener(null); + } + + @Test + public void checkValue() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1); + + assertThat(mTestObserver.getSettingsValue()).isEqualTo("1"); + } + + + private static class FakeSecureSettingsContentObserver extends + SecureSettingsContentObserver { + + protected FakeSecureSettingsContentObserver(Context context, + String secureSettingsKey) { + super(context, secureSettingsKey); + } + + @Override + void onValueChanged(Object listener, String value) { + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java new file mode 100644 index 0000000000000..a83f0382ba7bc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -0,0 +1,204 @@ +/* + * 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.floatingmenu; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; + +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; + +/** Test for {@link AccessibilityFloatingMenuController}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { + + private static final String TEST_A11Y_BTN_TARGETS = "Magnification"; + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + private AccessibilityFloatingMenuController mController; + private AccessibilityButtonTargetsObserver mTargetsObserver; + private AccessibilityButtonModeObserver mModeObserver; + @Mock + private AccessibilityManager mMockA11yManager; + + @Test + public void initController_registerListeners() { + mController = setUpController(); + + verify(mTargetsObserver).addListener( + any(AccessibilityButtonTargetsObserver.TargetsChangedListener.class)); + verify(mModeObserver).addListener( + any(AccessibilityButtonModeObserver.ModeChangedListener.class)); + verify(mMockA11yManager).addAccessibilityStateChangeListener(any( + AccessibilityManager.AccessibilityStateChangeListener.class)); + } + + @Test + public void initController_accessibilityManagerEnabled_showWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + when(mMockA11yManager.isEnabled()).thenReturn(true); + + mController = setUpController(); + + assertThat(mController.mFloatingMenu).isNotNull(); + verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController); + } + + @Test + public void initController_accessibilityManagerDisabledThenCallbackToEnabled_showWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + when(mMockA11yManager.isEnabled()).thenReturn(false); + + mController = setUpController(); + mController.onAccessibilityStateChanged(true); + + assertThat(mController.mFloatingMenu).isNotNull(); + verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController); + } + + @Test + public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + mController = setUpController(); + + mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + assertThat(mController.mFloatingMenu).isNotNull(); + } + + @Test + public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, ""); + mController = setUpController(); + + mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + + assertThat(mController.mFloatingMenu).isNull(); + } + + @Test + public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_showWidget() { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS); + mController = setUpController(); + + mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + assertThat(mController.mFloatingMenu).isNull(); + } + + @Test + public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, ""); + mController = setUpController(); + + mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + + assertThat(mController.mFloatingMenu).isNull(); + } + + @Test + public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + mController = setUpController(); + + mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); + + assertThat(mController.mFloatingMenu).isNotNull(); + } + + @Test + public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + mController = setUpController(); + + mController.onAccessibilityButtonTargetsChanged(""); + + assertThat(mController.mFloatingMenu).isNull(); + } + + @Test + public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_showWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + mController = setUpController(); + + mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); + + assertThat(mController.mFloatingMenu).isNull(); + } + + @Test + public void onAccessibilityButtonTargetsChanged_buttonModeAndNoButtonTargets_destroyWidget() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + mController = setUpController(); + + mController.onAccessibilityButtonTargetsChanged(""); + + assertThat(mController.mFloatingMenu).isNull(); + } + + private AccessibilityFloatingMenuController setUpController() { + mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class)); + mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class)); + mContext.addMockSystemService(AccessibilityManager.class, mMockA11yManager); + + return new AccessibilityFloatingMenuController(mContext, mTargetsObserver, + mModeObserver); + } +} 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 22c553b764b24..f0c48bd198ffd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -67,6 +67,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; @@ -259,6 +260,7 @@ public class NavigationBarTest extends SysuiTestCase { new MetricsLogger(), mOverviewProxyService, mock(NavigationModeController.class), + mock(AccessibilityButtonModeObserver.class), mock(StatusBarStateController.class), mMockSysUiState, mBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 18bdd41dada05..0b454bfa8b691 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -78,6 +78,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; @@ -210,6 +211,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationMediaManager mNotificationMediaManager; @Mock private NavigationBarController mNavigationBarController; + @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController; @Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier; @Mock private SysuiColorExtractor mColorExtractor; @Mock private ColorExtractor.GradientColors mGradientColors; @@ -385,6 +387,7 @@ public class StatusBarTest extends SysuiTestCase { mVisualStabilityManager, mDeviceProvisionedController, mNavigationBarController, + mAccessibilityFloatingMenuController, () -> mAssistManager, configurationController, mNotificationShadeWindowController,