diff options
20 files changed, 1036 insertions, 3 deletions
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index d10ff402aa3a..5d4078d3419b 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -252,4 +252,8 @@ <!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW --> <bool name="def_enable_non_resizable_multi_window">true</bool> + + <!-- Default for Settings.Secure.ACCESSIBILITY_BUTTON_MODE --> + <integer name="def_accessibility_button_mode">1</integer> + </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 400742ba7d78..081f3f673702 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 78180a7a80a8..685896523e36 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2673,6 +2673,12 @@ <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] --> <string name="magnification_mode_switch_click_label">Switch</string> + <!-- Accessibility floating menu strings --> + <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] --> + <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string> + <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] --> + <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string> + <!-- Device Controls strings --> <!-- Device Controls empty state, title [CHAR LIMIT=30] --> <string name="quick_controls_title">Device controls</string> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index ae4c8e5a3327..06b486ec43d0 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<IWindowManager> mIWindowManager; @Inject Lazy<OverviewProxyService> mOverviewProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; + @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver; + @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController; @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; @Inject Lazy<VibratorHelper> mVibratorHelper; @Inject Lazy<IStatusBarService> mIStatusBarService; @@ -294,6 +299,7 @@ public class Dependency { @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; @Inject Lazy<AppOpsController> mAppOpsController; @Inject Lazy<NavigationBarController> mNavigationBarController; + @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; @Inject Lazy<NotificationGroupAlertTransferHelper> 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 000000000000..9bedb1ea7563 --- /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<AccessibilityButtonModeObserver.ModeChangedListener> { + + 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 000000000000..b32ebccdd7db --- /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<AccessibilityButtonTargetsObserver.TargetsChangedListener> { + + /** 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 000000000000..4f8d8666a24a --- /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 <T> The listener type + */ +public abstract class SecureSettingsContentObserver<T> { + + private final ContentResolver mContentResolver; + @VisibleForTesting + final ContentObserver mContentObserver; + + private final String mKey; + + @VisibleForTesting + final List<T> 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 000000000000..112e9cae0a21 --- /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 8e344d2c2df7..1a729295165c 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, @@ -257,6 +262,16 @@ public class DependencyProvider { /** */ @Provides @SysUISingleton + public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController( + Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, + AccessibilityButtonModeObserver accessibilityButtonModeObserver) { + return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver, + accessibilityButtonModeObserver); + } + + /** */ + @Provides + @SysUISingleton public ConfigurationController provideConfigurationController(Context context) { return new ConfigurationControllerImpl(context); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index a0906dfdfd5e..9d43e0c13320 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<Pip> 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 ca69e6de2d0a..50efa8def5bf 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 c6ef8a3d7ba4..6dd00a5ec70d 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<AssistManager> 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 b572c57590ae..17bb449ec5f4 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<AssistManager> 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 000000000000..01b7adefbacf --- /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 000000000000..1e49fc998ea4 --- /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 000000000000..5b1c441a71a1 --- /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<Object> { + + 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 000000000000..a83f0382ba7b --- /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 e761da4c4675..c29b812dde7a 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 22c553b764b2..f0c48bd198ff 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 18bdd41dada0..0b454bfa8b69 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, |