Perform merging for A11Y_SHORTCUT B&R.
The restored value merges with the previous (pre-restore) value.
Also fixes some bugs:
- Prevents writing `null` to the setting during early SUW (by skipping
writing when old==new). This was causing the value to be written as
not-overwritable, which would cause B&R to fail for this setting
altogether.
- Writes the value of the default service to the setting if the user
manually triggers the shortcut and enables the default service during
SUW. This gets the device to a healthy state where the setting value
matches the observed behavior, rather than permanently relying on the
confusing "null means default" behavior for this user.
Bug: 341374402
Flag: android.view.accessibility.restore_a11y_shortcut_target_service
Test: atest SettingsHelperRestoreTest
Test: atest AccessibilityManagerServiceTest
Test: atest AccessibilityShortcutControllerTest
Test: Trigger volume keys shortcut twice during SUW.
Observe the setting contains the default service.
Test: Create a backup with Setting = A, then factory reset.
- Keep S empty, then restore. Observe S = A.
- Set S = A, then restore. Observe S = A.
- Set S = B, then restore. Observe S = A:B.
Change-Id: I73a5fcf36cdb45dd9a66b5823c1cc8fd4c0cc446
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index da2bf9d..86c68fe 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -153,6 +153,16 @@
}
flag {
+ name: "restore_a11y_shortcut_target_service"
+ namespace: "accessibility"
+ description: "Perform merging and other bug fixes for SettingsProvider restore of ACCESSIBILITY_SHORTCUT_TARGET_SERVICES secure setting"
+ bug: "341374402"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "support_system_pinch_zoom_opt_out_apis"
namespace: "accessibility"
description: "Feature flag for declaring system pinch zoom opt-out apis"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 06ae11fee..33b4e4a 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -28,6 +28,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -240,6 +241,7 @@
/**
* Called when the accessibility shortcut is activated
*/
+ @SuppressLint("MissingPermission")
public void performAccessibilityShortcut() {
Slog.d(TAG, "Accessibility shortcut activated");
final ContentResolver cr = mContext.getContentResolver();
@@ -274,6 +276,9 @@
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.SHOWN,
userId);
} else {
+ if (Flags.restoreA11yShortcutTargetService()) {
+ enableDefaultHardwareShortcut(userId);
+ }
playNotificationTone();
if (mAlertDialog != null) {
mAlertDialog.dismiss();
@@ -349,34 +354,7 @@
.setMessage(getShortcutWarningMessage(targets))
.setCancelable(false)
.setNegativeButton(R.string.accessibility_shortcut_on,
- (DialogInterface d, int which) -> {
- String targetServices = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
- String defaultService = mContext.getString(
- R.string.config_defaultAccessibilityService);
- // If the targetServices is null, means the user enables a
- // shortcut for the default service by triggering the volume keys
- // shortcut in the SUW instead of intentionally configuring the
- // shortcut on UI.
- if (targetServices == null && !TextUtils.isEmpty(defaultService)) {
- // The defaultService in the string resource could be a shorten
- // form like com.google.android.marvin.talkback/.TalkBackService.
- // Converts it to the componentName for consistency before saving
- // to the Settings.
- final ComponentName configDefaultService =
- ComponentName.unflattenFromString(defaultService);
- if (Flags.migrateEnableShortcuts()) {
- am.enableShortcutsForTargets(true, HARDWARE,
- Set.of(configDefaultService.flattenToString()), userId);
- } else {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- configDefaultService.flattenToString(),
- userId);
- }
- }
- })
+ (DialogInterface d, int which) -> enableDefaultHardwareShortcut(userId))
.setPositiveButton(R.string.accessibility_shortcut_off,
(DialogInterface d, int which) -> {
Set<String> targetServices =
@@ -509,6 +487,47 @@
}
}
+ /**
+ * Writes {@link R.string#config_defaultAccessibilityService} to the
+ * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE} Setting if
+ * that Setting is currently {@code null}.
+ *
+ * <p>If {@code ACCESSIBILITY_SHORTCUT_TARGET_SERVICE} is {@code null} then the
+ * user triggered the shortcut during Setup Wizard <i>before</i> directly
+ * enabling the shortcut in the Settings UI of Setup Wizard.
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ private void enableDefaultHardwareShortcut(int userId) {
+ final AccessibilityManager accessibilityManager = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext);
+ final String targetServices = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ if (targetServices != null) {
+ // Do not write if the Setting was already configured.
+ return;
+ }
+ final String defaultService = mContext.getString(
+ R.string.config_defaultAccessibilityService);
+ // The defaultService in the string resource could be a shortened
+ // form: "com.android.accessibility.package/.MyService". Convert it to
+ // the component name form for consistency before writing to the Setting.
+ final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
+ ? null : ComponentName.unflattenFromString(defaultService);
+ if (defaultServiceComponent == null) {
+ // Default service is invalid, so nothing we can do here.
+ return;
+ }
+ if (Flags.migrateEnableShortcuts()) {
+ accessibilityManager.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(defaultServiceComponent.flattenToString()), userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ defaultServiceComponent.flattenToString(), userId);
+ }
+ }
+
private boolean performTtsPrompt(AlertDialog alertDialog) {
final String serviceName = getShortcutFeatureDescription(false /* no summary */);
final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index d560ef2..6b9dbba 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -634,6 +634,42 @@
}
@Test
+ @EnableFlags({
+ Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS,
+ Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE})
+ public void testOnAccessibilityShortcut_settingNull_dialogShown_enablesDefaultShortcut()
+ throws Exception {
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ // Setting is only `null` during SUW.
+ Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null);
+ getController().performAccessibilityShortcut();
+
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
+ @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
+ public void testOnAccessibilityShortcut_settingNull_dialogShown_writesDefaultSetting()
+ throws Exception {
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ // Setting is only `null` during SUW.
+ Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null);
+ getController().performAccessibilityShortcut();
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
+ }
+
+ @Test
public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
frameworkFeatureMap =
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 2e9075c..7be15af 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -88,7 +88,7 @@
private static final ArraySet<String> sBroadcastOnRestore;
private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
static {
- sBroadcastOnRestore = new ArraySet<String>(9);
+ sBroadcastOnRestore = new ArraySet<String>(12);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -99,6 +99,7 @@
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS);
+ sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
sBroadcastOnRestore.add(Settings.Secure.SCREEN_RESOLUTION_MODE);
sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
@@ -240,6 +241,11 @@
// Don't write it to setting. Let the broadcast receiver in
// AccessibilityManagerService handle restore/merging logic.
return;
+ } else if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()
+ && Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) {
+ // Don't write it to setting. Let the broadcast receiver in
+ // AccessibilityManagerService handle restore/merging logic.
+ return;
}
// Default case: write the restored value to settings
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index 2f8cf4b..f64f72a 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -26,6 +26,8 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.SettingsStringUtil;
@@ -35,6 +37,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -48,6 +51,10 @@
*/
@RunWith(AndroidJUnit4.class)
public class SettingsHelperRestoreTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final float FLOAT_TOLERANCE = 0.01f;
private Context mContext;
@@ -202,4 +209,32 @@
Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, /* defaultValue= */ 0))
.isEqualTo(Build.VERSION.SDK_INT);
}
+
+ @Test
+ @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
+ public void restoreAccessibilityShortcutTargetService_broadcastSent()
+ throws ExecutionException, InterruptedException {
+ BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
+ mContext);
+ final String settingName = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+ final String restoredValue = "com.android.a11y/Service";
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ interceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED);
+
+ mSettingsHelper.restoreValue(
+ interceptingContext,
+ mContentResolver,
+ new ContentValues(2),
+ Settings.Secure.getUriFor(settingName),
+ settingName,
+ restoredValue,
+ Build.VERSION.SDK_INT);
+
+ Intent intentReceived = futureIntent.get();
+ assertThat(intentReceived.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE))
+ .isEqualTo(restoredValue);
+ assertThat(intentReceived.getIntExtra(
+ Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, /* defaultValue= */ 0))
+ .isEqualTo(Build.VERSION.SDK_INT);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a15d2ca..c55e9ea 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -65,6 +65,7 @@
import android.annotation.Nullable;
import android.annotation.PermissionManuallyEnforced;
import android.annotation.RequiresNoPermission;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.AlertDialog;
@@ -857,6 +858,7 @@
mPackageMonitor = monitor;
}
+ @SuppressLint("MissingPermission")
private void registerBroadcastReceivers() {
// package changes
mPackageMonitor = new ManagerPackageMonitor(this);
@@ -890,33 +892,47 @@
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
- if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
- synchronized (mLock) {
- restoreEnabledAccessibilityServicesLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
- intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
- 0));
+ if (which == null) {
+ return;
+ }
+ final String previousValue =
+ intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+ final String newValue =
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+ final int restoredFromSdk =
+ intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, 0);
+ switch (which) {
+ case Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES -> {
+ synchronized (mLock) {
+ restoreEnabledAccessibilityServicesLocked(
+ previousValue, newValue, restoredFromSdk);
+ }
}
- } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) {
- synchronized (mLock) {
- restoreLegacyDisplayMagnificationNavBarIfNeededLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE),
- intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT,
- 0));
+ case ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED -> {
+ synchronized (mLock) {
+ restoreLegacyDisplayMagnificationNavBarIfNeededLocked(
+ newValue, restoredFromSdk);
+ }
}
- } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) {
- synchronized (mLock) {
- restoreAccessibilityButtonTargetsLocked(
- intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+ case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> {
+ synchronized (mLock) {
+ restoreAccessibilityButtonTargetsLocked(
+ previousValue, newValue);
+ }
}
- } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) {
- if (!android.view.accessibility.Flags.a11yQsShortcut()) {
- return;
+ case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+ restoreAccessibilityQsTargets(newValue);
}
- restoreAccessibilityQsTargets(
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+ case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> {
+ if (!android.view.accessibility.Flags
+ .restoreA11yShortcutTargetService()) {
+ return;
+ }
+ restoreAccessibilityShortcutTargetService(previousValue, newValue);
+ }
}
}
}
@@ -2079,6 +2095,31 @@
}
}
+ /**
+ * Merges the old and restored value of
+ * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE}.
+ */
+ private void restoreAccessibilityShortcutTargetService(
+ String oldValue, String restoredValue) {
+ final Set<String> targetsFromSetting = new ArraySet<>();
+ readColonDelimitedStringToSet(oldValue, str -> str,
+ targetsFromSetting, /*doMerge=*/false);
+ readColonDelimitedStringToSet(restoredValue, str -> str,
+ targetsFromSetting, /*doMerge=*/true);
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+ final Set<String> shortcutTargets =
+ userState.getShortcutTargetsLocked(UserShortcutType.HARDWARE);
+ shortcutTargets.clear();
+ shortcutTargets.addAll(targetsFromSetting);
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ UserHandle.USER_SYSTEM, targetsFromSetting, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ onUserStateChangedLocked(userState);
+ }
+ }
+
private int getClientStateLocked(AccessibilityUserState userState) {
return userState.getClientStateLocked(
mUiAutomationManager.canIntrospect(),
@@ -2605,12 +2646,35 @@
}
builder.append(str);
}
+ final String builderValue = builder.toString();
+ final String settingValue = TextUtils.isEmpty(builderValue)
+ ? defaultEmptyString : builderValue;
+ if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
+ final String currentValue = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), settingName, userId);
+ if (Objects.equals(settingValue, currentValue)) {
+ // This logic exists to fix a bug where AccessibilityManagerService was writing
+ // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
+ // during setup, due to a race condition in package scanning making A11yMS think
+ // that the default service was not installed.
+ //
+ // Writing `null` was implicitly causing that Setting to have the default
+ // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
+ // Setting altogether.
+ //
+ // The "quick fix" here is to not write `null` if the existing value is already
+ // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
+ // that allows override-by-restore, but the full repercussions of using that here
+ // have not yet been evaluated.
+ // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
+ // "overridable by restore" when writing secure settings.
+ return;
+ }
+ }
final long identity = Binder.clearCallingIdentity();
try {
- final String settingValue = builder.toString();
Settings.Secure.putStringForUser(mContext.getContentResolver(),
- settingName,
- TextUtils.isEmpty(settingValue) ? defaultEmptyString : settingValue, userId);
+ settingName, settingValue, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index ca15aa2..78cd2c1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -1590,12 +1590,10 @@
userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
- Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.ACCESSIBILITY_QS_TARGETS)
- .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, colorInversionTile);
- sendBroadcastToAccessibilityManagerService(intent);
- mTestableLooper.processAllMessages();
+ broadcastSettingRestored(
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ /*previousValue=*/null,
+ /*newValue=*/colorInversionTile);
assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets())
.containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile));
@@ -1613,12 +1611,10 @@
userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
- Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.ACCESSIBILITY_QS_TARGETS)
- .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, colorInversionTile);
- sendBroadcastToAccessibilityManagerService(intent);
- mTestableLooper.processAllMessages();
+ broadcastSettingRestored(
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ /*previousValue=*/null,
+ /*newValue=*/colorInversionTile);
assertThat(userState.getA11yQsTargets())
.containsExactlyElementsIn(Set.of(daltonizerTile));
@@ -1679,12 +1675,55 @@
assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
- new String[]{ "FOO", "BAR"},
+ new String[]{"FOO", "BAR"},
UserHandle.USER_SYSTEM,
false
));
}
+ @Test
+ @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
+ public void restoreA11yShortcutTargetService_targetsMerged() {
+ final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+ final String otherPrevious = TARGET_MAGNIFICATION;
+ final String combinedPrevious = String.join(":", servicePrevious, otherPrevious);
+ final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
+ final AccessibilityUserState userState = new AccessibilityUserState(
+ UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ setupShortcutTargetServices(userState);
+
+ broadcastSettingRestored(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ /*previousValue=*/combinedPrevious,
+ /*newValue=*/serviceRestored);
+
+ final Set<String> expected = Set.of(servicePrevious, otherPrevious, serviceRestored);
+ assertThat(readStringsFromSetting(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
+ .containsExactlyElementsIn(expected);
+ assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
+ .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
+ .containsExactlyElementsIn(expected);
+ }
+
+ private Set<String> readStringsFromSetting(String setting) {
+ final Set<String> result = new ArraySet<>();
+ mA11yms.readColonDelimitedSettingToSet(
+ setting, UserHandle.USER_SYSTEM, str -> str, result);
+ return result;
+ }
+
+ private void broadcastSettingRestored(String setting, String previousValue, String newValue) {
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .putExtra(Intent.EXTRA_SETTING_NAME, setting)
+ .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue)
+ .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, newValue);
+ sendBroadcastToAccessibilityManagerService(intent);
+ mTestableLooper.processAllMessages();
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
return mockAccessibilityServiceInfo(
@@ -1738,6 +1777,10 @@
}
private void setupShortcutTargetServices() {
+ setupShortcutTargetServices(mA11yms.getCurrentUserState());
+ }
+
+ private void setupShortcutTargetServices(AccessibilityUserState userState) {
AccessibilityServiceInfo alwaysOnServiceInfo = mockAccessibilityServiceInfo(
TARGET_ALWAYS_ON_A11Y_SERVICE,
/* isSystemApp= */ false,
@@ -1748,9 +1791,9 @@
TARGET_STANDARD_A11Y_SERVICE,
/* isSystemApp= */ false,
/* isAlwaysOnService= */ false);
- mA11yms.getCurrentUserState().mInstalledServices.addAll(
+ userState.mInstalledServices.addAll(
List.of(alwaysOnServiceInfo, standardServiceInfo));
- mA11yms.getCurrentUserState().updateTileServiceMapForAccessibilityServiceLocked();
+ userState.updateTileServiceMapForAccessibilityServiceLocked();
}
private void sendBroadcastToAccessibilityManagerService(Intent intent) {