diff options
6 files changed, 157 insertions, 0 deletions
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index 7ec3d243529f..bf4f60d84e4d 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -60,6 +60,7 @@ android_test { // because this test is not an instrumentation test. (because the target runs in the system process.) "SettingsProviderLib", "androidx.test.rules", + "frameworks-base-testutils", "device_config_service_flags_java", "flag-junit", "junit", diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 3e0d05cd9ecf..1eb04ac1c181 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -98,6 +98,7 @@ public class SettingsHelper { sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); sBroadcastOnRestoreSystemUI = new ArraySet<String>(2); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES); @@ -229,6 +230,10 @@ public class SettingsHelper { } else if (Settings.System.ACCELEROMETER_ROTATION.equals(name) && shouldSkipAutoRotateRestore()) { return; + } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.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 197788e11973..2f8cf4b3d034 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java @@ -16,23 +16,31 @@ package com.android.providers.settings; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; +import android.provider.SettingsStringUtil; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.test.BroadcastInterceptingContext; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.concurrent.ExecutionException; + /** * Tests for {@link SettingsHelper#restoreValue(Context, ContentResolver, ContentValues, Uri, * String, String, int)}. Specifically verifies that we restore critical accessibility settings only @@ -165,4 +173,33 @@ public class SettingsHelperRestoreTest { assertEquals(restoreSettingValue, Settings.Secure.getInt(mContentResolver, settingName)); } + + @Test + public void restoreAccessibilityQsTargets_broadcastSent() + throws ExecutionException, InterruptedException { + BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext( + mContext); + final String settingName = Settings.Secure.ACCESSIBILITY_QS_TARGETS; + final String restoreSettingValue = "com.android.server.accessibility/ColorInversion" + + SettingsStringUtil.DELIMITER + + "com.android.server.accessibility/ColorCorrectionTile"; + BroadcastInterceptingContext.FutureIntent futureIntent = + interceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); + + mSettingsHelper.restoreValue( + interceptingContext, + mContentResolver, + new ContentValues(2), + Settings.Secure.getUriFor(settingName), + settingName, + restoreSettingValue, + Build.VERSION.SDK_INT); + + Intent intentReceived = futureIntent.get(); + assertThat(intentReceived.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)) + .isEqualTo(restoreSettingValue); + 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 f3e53fe301d4..2acb8ff9ba97 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1016,6 +1016,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); } + } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) { + if (!android.view.accessibility.Flags.a11yQsShortcut()) { + return; + } + restoreAccessibilityQsTargets( + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); } } } @@ -2154,6 +2160,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub onUserStateChangedLocked(userState); } + /** + * User could configure accessibility shortcut during the SUW before restoring user data. + * Merges the current value and the new value to make sure we don't lost the setting the user's + * preferences of accessibility qs shortcut updated in SUW are not lost. + * + * Called only during settings restore; currently supports only the owner user + * TODO: http://b/22388012 + */ + private void restoreAccessibilityQsTargets(String newValue) { + synchronized (mLock) { + final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); + final Set<String> mergedTargets = userState.getA11yQsTargets(); + readColonDelimitedStringToSet(newValue, str -> str, mergedTargets, + /* doMerge = */ true); + + userState.updateA11yQsTargetLocked(mergedTargets); + persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_QS_TARGETS, + UserHandle.USER_SYSTEM, mergedTargets, str -> str); + scheduleNotifyClientsOfServicesStateChangeLocked(userState); + onUserStateChangedLocked(userState); + } + } + private int getClientStateLocked(AccessibilityUserState userState) { return userState.getClientStateLocked( mUiAutomationManager.canIntrospect(), diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 9a1d3793e447..7008e8e0f0ba 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -112,6 +112,10 @@ class AccessibilityUserState { * TileService's or the a11y framework tile component names (e.g. * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the * A11y Feature's component names. + * <p/> + * In addition, {@link #mA11yTilesInQsPanel} stores what's on the QS Panel, whereas + * {@link #mAccessibilityQsTargets} stores the targets that configured qs as their shortcut and + * also grant full device control permission. */ private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>(); 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 eb8950384f10..5b645261fd01 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -50,9 +50,11 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.app.PendingIntent; import android.app.RemoteAction; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -67,6 +69,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.LocaleList; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -74,6 +77,7 @@ import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.util.ArrayMap; import android.util.ArraySet; import android.view.Display; import android.view.DisplayAdjustments; @@ -123,6 +127,7 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -1465,6 +1470,52 @@ public class AccessibilityManagerServiceTest { AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString()); } + @Test + @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) + public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() { + String daltonizerTile = + AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); + String colorInversionTile = + AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString(); + final AccessibilityUserState userState = new AccessibilityUserState( + UserHandle.USER_SYSTEM, mTestableContext, mA11yms); + 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(); + + assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets()) + .containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile)); + } + + @Test + @RequiresFlagsDisabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) + public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() { + String daltonizerTile = + AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); + String colorInversionTile = + AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString(); + final AccessibilityUserState userState = new AccessibilityUserState( + UserHandle.USER_SYSTEM, mTestableContext, mA11yms); + 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(); + + assertThat(userState.getA11yQsTargets()) + .containsExactlyElementsIn(Set.of(daltonizerTile)); + } + private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName) { return mockAccessibilityServiceInfo( @@ -1543,6 +1594,14 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserState().updateTileServiceMapForAccessibilityServiceLocked(); } + private void sendBroadcastToAccessibilityManagerService(Intent intent) { + if (!mTestableContext.getBroadcastReceivers().containsKey(intent.getAction())) { + return; + } + mTestableContext.getBroadcastReceivers().get(intent.getAction()).forEach( + broadcastReceiver -> broadcastReceiver.onReceive(mTestableContext, intent)); + } + public static class FakeInputFilter extends AccessibilityInputFilter { FakeInputFilter(Context context, AccessibilityManagerService service) { @@ -1553,6 +1612,7 @@ public class AccessibilityManagerServiceTest { private static class A11yTestableContext extends TestableContext { private final Context mMockContext; + private final Map<String, List<BroadcastReceiver>> mBroadcastReceivers = new ArrayMap<>(); A11yTestableContext(Context base) { super(base); @@ -1564,8 +1624,29 @@ public class AccessibilityManagerServiceTest { mMockContext.startActivityAsUser(intent, options, user); } + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + Iterator<String> actions = filter.actionsIterator(); + if (actions != null) { + while (actions.hasNext()) { + String action = actions.next(); + List<BroadcastReceiver> actionReceivers = + mBroadcastReceivers.getOrDefault(action, new ArrayList<>()); + actionReceivers.add(receiver); + mBroadcastReceivers.put(action, actionReceivers); + } + } + return super.registerReceiverAsUser( + receiver, user, filter, broadcastPermission, scheduler); + } + Context getMockContext() { return mMockContext; } + + Map<String, List<BroadcastReceiver>> getBroadcastReceivers() { + return mBroadcastReceivers; + } } } |