diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 74eee6300b2a5..2245ee4f28213 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -4692,24 +4692,16 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 185) { // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it - // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT. + // to ACCESSIBILITY_BUTTON_TARGETS. final SettingsState secureSettings = getSecureSettingsLocked(userId); final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked( Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); if ("1".equals(magnifyNavbarEnabled.getValue())) { secureSettings.insertSettingLocked( - Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + Secure.ACCESSIBILITY_BUTTON_TARGETS, ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER, null /* tag */, false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); - } else { - // Clear a11y button targets list setting. A11yManagerService will end up - // adding all legacy enabled services that want the button to the list, so - // there's no need to keep tracking them. - secureSettings.insertSettingLocked( - Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, - null, null /* tag */, false /* makeDefault */, - SettingsState.SYSTEM_PACKAGE_NAME); } secureSettings.deleteSettingLocked( Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index da9bdf3262d54..6b852adce0f13 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1081,6 +1081,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ pw.append(", eventTypes=" + AccessibilityEvent.eventTypeToString(mEventTypes)); pw.append(", notificationTimeout=" + mNotificationTimeout); + pw.append(", requestA11yBtn=" + mRequestAccessibilityButton); pw.append("]"); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1a72cf023453d..30460cc7f05d2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -23,6 +23,7 @@ import static android.view.accessibility.AccessibilityManager.ShortcutType; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain; import android.Manifest; import android.accessibilityservice.AccessibilityGestureEvent; @@ -876,6 +877,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub throw new SecurityException("Caller does not hold permission " + android.Manifest.permission.STATUS_BAR_SERVICE); } + if (targetName == null) { + synchronized (mLock) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + targetName = userState.getTargetAssignedToAccessibilityButton(); + } + } mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::performAccessibilityShortcutInternal, this, displayId, ACCESSIBILITY_BUTTON, targetName)); @@ -1828,7 +1835,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState); - somethingChanged |= readAccessibilityButtonSettingsLocked(userState); + somethingChanged |= readAccessibilityButtonTargetsLocked(userState); + somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState); somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); return somethingChanged; } @@ -1948,9 +1956,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } - private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) { + private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) { final Set targetsFromSetting = new ArraySet<>(); - readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userState.mUserId, targetsFromSetting, str -> str); final Set currentTargets = @@ -1964,6 +1972,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } + private boolean readAccessibilityButtonTargetComponentLocked(AccessibilityUserState userState) { + final String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId); + if (TextUtils.isEmpty(componentId)) { + if (userState.getTargetAssignedToAccessibilityButton() == null) { + return false; + } + userState.setTargetAssignedToAccessibilityButton(null); + return true; + } + if (componentId.equals(userState.getTargetAssignedToAccessibilityButton())) { + return false; + } + userState.setTargetAssignedToAccessibilityButton(componentId); + return true; + } + private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) { final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser( mContext.getContentResolver(), @@ -1984,7 +2009,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * Check if the targets that will be enabled by the accessibility shortcut key is installed. + * Check if the target that will be enabled by the accessibility shortcut key is installed. * If it isn't, remove it from the list and associated setting so a side loaded service can't * spoof the package name of the default service. */ @@ -2145,7 +2170,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * 1) Update accessibility button availability to accessibility services. - * 2) Check if the targets that will be enabled by the accessibility button is installed. + * 2) Check if the target that will be enabled by the accessibility button is installed. * If it isn't, remove it from the list and associated setting so a side loaded service can't * spoof the package name of the default service. */ @@ -2172,8 +2197,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // Update setting key with new value. - persistColonDelimitedSetToSettingLocked( - Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userState.mUserId, currentTargets, str -> str); scheduleNotifyClientsOfServicesStateChangeLocked(userState); } @@ -2182,7 +2206,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * 1) Check if the service assigned to accessibility button target sdk version > Q. * If it isn't, remove it from the list and associated setting. * (It happens when an accessibility service package is downgraded.) - * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is + * 2) For a service targeting sdk version > Q and requesting a11y button, it should be in the + * enabled list if's assigned to a11y button. + * (It happens when an accessibility service package is same graded, and updated requesting + * a11y button flag) + * 3) Check if an enabled service targeting sdk version > Q and requesting a11y button is * assigned to a shortcut. If it isn't, assigns it to the accessibility button. * (It happens when an enabled accessibility service package is upgraded.) * @@ -2207,11 +2235,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo - .targetSdkVersion > Build.VERSION_CODES.Q) { - return false; + .targetSdkVersion <= Build.VERSION_CODES.Q) { + // A11y services targeting sdk version <= Q should not be in the list. + Slog.v(LOG_TAG, "Legacy service " + componentName + + " should not in the button"); + return true; } - // A11y services targeting sdk version <= Q should not be in the list. - return true; + final boolean requestA11yButton = (serviceInfo.flags + & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; + if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) { + // An a11y service targeting sdk version > Q and request A11y button and is assigned + // to a11y btn should be in the enabled list. + Slog.v(LOG_TAG, "Service requesting a11y button and be assigned to the button" + + componentName + " should be enabled state"); + return true; + } + return false; }); boolean changed = (lastSize != buttonTargets.size()); lastSize = buttonTargets.size(); @@ -2234,15 +2273,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) { return; } - final String serviceName = serviceInfo.getComponentName().flattenToString(); + final String serviceName = componentName.flattenToString(); if (TextUtils.isEmpty(serviceName)) { return; } - if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) { + if (doesShortcutTargetsStringContain(buttonTargets, serviceName) + || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)) { return; } // For enabled a11y services targeting sdk version > Q and requesting a11y button should // be assigned to a shortcut. + Slog.v(LOG_TAG, "A enabled service requesting a11y button " + componentName + + " should be assign to the button or shortcut."); buttonTargets.add(serviceName); }); changed |= (lastSize != buttonTargets.size()); @@ -2251,8 +2293,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // Update setting key with new value. - persistColonDelimitedSetToSettingLocked( - Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, + persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userState.mUserId, buttonTargets, str -> str); scheduleNotifyClientsOfServicesStateChangeLocked(userState); } @@ -2353,12 +2394,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } // In case the caller specified a target name - if (targetName != null) { - if (!shortcutTargets.contains(targetName)) { - Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); - return; - } - } else { + if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) { + Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); + targetName = null; + } + if (targetName == null) { // In case there are many targets assigned to the given shortcut. if (shortcutTargets.size() > 1) { showAccessibilityTargetsSelection(displayId, shortcutType); @@ -2983,6 +3023,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT); + private final Uri mAccessibilityButtonTargetsUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS); @@ -3015,6 +3058,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mAccessibilityButtonTargetsUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( @@ -3061,7 +3106,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub onUserStateChangedLocked(userState); } } else if (mAccessibilityButtonComponentIdUri.equals(uri)) { - if (readAccessibilityButtonSettingsLocked(userState)) { + if (readAccessibilityButtonTargetComponentLocked(userState)) { + onUserStateChangedLocked(userState); + } + } else if (mAccessibilityButtonTargetsUri.equals(uri)) { + if (readAccessibilityButtonTargetsLocked(userState)) { onUserStateChangedLocked(userState); } } else if (mUserNonInteractiveUiTimeoutUri.equals(uri) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 4e7da97bab36c..bad649ab6b026 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -49,6 +49,7 @@ import com.android.internal.accessibility.AccessibilityShortcutController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -96,6 +97,8 @@ class AccessibilityUserState { private ComponentName mServiceChangingSoftKeyboardMode; + private String mTargetAssignedToAccessibilityButton; + private boolean mBindInstantServiceAllowed; private boolean mIsAutoclickEnabled; private boolean mIsDisplayMagnificationEnabled; @@ -152,6 +155,7 @@ class AccessibilityUserState { mTouchExplorationGrantedServices.clear(); mAccessibilityShortcutKeyTargets.clear(); mAccessibilityButtonTargets.clear(); + mTargetAssignedToAccessibilityButton = null; mIsTouchExplorationEnabled = false; mServiceHandlesDoubleTap = false; mRequestMultiFingerGestures = false; @@ -469,6 +473,8 @@ class AccessibilityUserState { } } pw.println("}"); + pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton); + pw.println("}"); pw.append(" Bound services:{"); final int serviceCount = mBoundServices.size(); for (int j = 0; j < serviceCount; j++) { @@ -716,4 +722,56 @@ class AccessibilityUserState { public void setUserNonInteractiveUiTimeoutLocked(int timeout) { mUserNonInteractiveUiTimeout = timeout; } + + /** + * Gets a shortcut target which is assigned to the accessibility button by the chooser + * activity. + * + * @return The flattened component name or the system class name of the shortcut target. + */ + public String getTargetAssignedToAccessibilityButton() { + return mTargetAssignedToAccessibilityButton; + } + + /** + * Sets a shortcut target which is assigned to the accessibility button by the chooser + * activity. + * + * @param target The flattened component name or the system class name of the shortcut target. + */ + public void setTargetAssignedToAccessibilityButton(String target) { + mTargetAssignedToAccessibilityButton = target; + } + + /** + * Whether or not the given target name is contained in the shortcut collection. Since the + * component name string format could be short or long, this function un-flatten the component + * name from the string in {@code shortcutTargets} and compared with the given target name. + * + * @param shortcutTargets The shortcut type. + * @param targetName The target name. + * @return {@code true} if the target is in the shortcut collection. + */ + public static boolean doesShortcutTargetsStringContain(Collection shortcutTargets, + String targetName) { + if (shortcutTargets == null || targetName == null) { + return false; + } + // Some system features, such as magnification, don't have component name. Using string + // compare first. + if (shortcutTargets.contains(targetName)) { + return true; + } + final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName); + if (targetComponentName == null) { + return false; + } + for (String stringName : shortcutTargets) { + if (!TextUtils.isEmpty(stringName) + && targetComponentName.equals(ComponentName.unflattenFromString(stringName))) { + return true; + } + } + return false; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index ac5169c7c715b..cdfa3947901e5 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -25,6 +25,8 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSI import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; +import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; @@ -41,6 +43,7 @@ import android.content.Context; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; +import android.util.ArraySet; import com.android.internal.util.test.FakeSettingsProvider; @@ -56,6 +59,12 @@ public class AccessibilityUserStateTest { private static final ComponentName COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "AccessibilityUserStateTest"); + private static final ComponentName COMPONENT_NAME1 = + new ComponentName("com.android.server.accessibility", + "com.android.server.accessibility.AccessibilityUserStateTest1"); + private static final ComponentName COMPONENT_NAME2 = + new ComponentName("com.android.server.accessibility", + "com.android.server.accessibility.AccessibilityUserStateTest2"); // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD private static final int STATE_HIDE_IME = 0; @@ -111,6 +120,7 @@ public class AccessibilityUserStateTest { mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME); mUserState.mAccessibilityShortcutKeyTargets.add(COMPONENT_NAME.flattenToString()); mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString()); + mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); mUserState.setDisplayMagnificationEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); @@ -129,6 +139,7 @@ public class AccessibilityUserStateTest { assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty()); assertTrue(mUserState.mAccessibilityShortcutKeyTargets.isEmpty()); assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty()); + assertNull(mUserState.getTargetAssignedToAccessibilityButton()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); assertFalse(mUserState.isDisplayMagnificationEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); @@ -285,6 +296,31 @@ public class AccessibilityUserStateTest { verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN)); } + @Test + public void doesShortcutTargetsStringContain_returnFalse() { + assertFalse(doesShortcutTargetsStringContain(null, null)); + assertFalse(doesShortcutTargetsStringContain(null, + COMPONENT_NAME.flattenToShortString())); + assertFalse(doesShortcutTargetsStringContain(new ArraySet<>(), null)); + + final ArraySet shortcutTargets = new ArraySet<>(); + shortcutTargets.add(COMPONENT_NAME.flattenToString()); + assertFalse(doesShortcutTargetsStringContain(shortcutTargets, + COMPONENT_NAME1.flattenToString())); + } + + @Test + public void isAssignedToShortcutLocked_withDifferentTypeComponentString_returnTrue() { + final ArraySet shortcutTargets = new ArraySet<>(); + shortcutTargets.add(COMPONENT_NAME1.flattenToShortString()); + shortcutTargets.add(COMPONENT_NAME2.flattenToString()); + + assertTrue(doesShortcutTargetsStringContain(shortcutTargets, + COMPONENT_NAME1.flattenToString())); + assertTrue(doesShortcutTargetsStringContain(shortcutTargets, + COMPONENT_NAME2.flattenToShortString())); + } + @Test public void isShortcutTargetInstalledLocked_returnTrue() { mUserState.mInstalledServices.add(mMockServiceInfo);