Merge "Use accessibilityManager#enableShortcutsForTargets when turning shorcuts off/on" into main
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index a0c405e..ddb8ee0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -24,8 +24,10 @@
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -53,6 +55,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.widget.Toast;
import com.android.internal.R;
@@ -328,11 +331,14 @@
warningToast.show();
}
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
private AlertDialog createShortcutWarningDialog(int userId) {
List<AccessibilityTarget> targets = getTargets(mContext, HARDWARE);
if (targets.size() == 0) {
return null;
}
+ final AccessibilityManager am = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext);
// Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
// Put "don't turn on" as the primary action.
@@ -360,10 +366,15 @@
// to the Settings.
final ComponentName configDefaultService =
ComponentName.unflattenFromString(defaultService);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- configDefaultService.flattenToString(),
- userId);
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(configDefaultService.flattenToString()), userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ configDefaultService.flattenToString(),
+ userId);
+ }
}
})
.setPositiveButton(R.string.accessibility_shortcut_off,
@@ -373,13 +384,16 @@
mContext,
HARDWARE,
userId);
-
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- mContext, targetServices, userId);
-
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(
+ false, HARDWARE, targetServices, userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+ userId);
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, targetServices, userId);
+ }
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ba1dffc..f088592 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -23,11 +23,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -35,6 +38,8 @@
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* Abstract base class for creating various target related to accessibility service, accessibility
* activity, and allowlisting features.
@@ -108,13 +113,21 @@
}
}
+ @SuppressLint("MissingPermission")
@Override
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
- if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
+ if (Flags.a11yQsShortcut()) {
+ final AccessibilityManager am =
+ getContext().getSystemService(AccessibilityManager.class);
+ am.enableShortcutsForTargets(
+ isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
} else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ if (isChecked) {
+ optInValueToSettings(getContext(), getShortcutType(), getId());
+ } else {
+ optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ }
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 7535979..f9efb50 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -17,17 +17,11 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
-import android.content.ComponentName;
import android.content.Context;
-import android.widget.Toast;
-import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -47,30 +41,10 @@
@Override
public void onCheckedChanged(boolean isChecked) {
- switch (getShortcutType()) {
- case SOFTWARE:
- onCheckedFromAccessibilityButton(isChecked);
- return;
- case HARDWARE:
- super.onCheckedChanged(isChecked);
- return;
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
- }
-
- private void onCheckedFromAccessibilityButton(boolean isChecked) {
- setShortcutEnabled(isChecked);
- final ComponentName componentName = ComponentName.unflattenFromString(getId());
- setAccessibilityServiceState(getContext(), componentName, isChecked);
-
- if (!isChecked) {
- optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
-
- final String warningText =
- getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
- getLabel());
- Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+ if (getShortcutType() == HARDWARE) {
+ super.onCheckedChanged(isChecked);
+ } else {
+ throw new IllegalStateException("Unexpected shortcut type");
}
}
}
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 365f348..5fab1a0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,6 +64,9 @@
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -71,6 +74,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Toast;
@@ -83,9 +87,11 @@
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -93,11 +99,14 @@
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -126,6 +135,7 @@
private @Mock TextToSpeech mTextToSpeech;
private @Mock Voice mVoice;
private @Mock Ringtone mRingtone;
+ private @Captor ArgumentCaptor<List<String>> mListCaptor;
private MockContentResolver mContentResolver;
private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
@@ -410,6 +420,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -423,6 +434,29 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
assertThat(
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
).isEmpty();
@@ -432,6 +466,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -443,6 +479,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -489,6 +527,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -503,15 +542,39 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.SHOWN);
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -526,9 +589,32 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).isEmpty();
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044c..a14d8e0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -19,8 +19,10 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,8 +33,12 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +53,13 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
/**
* Unit Tests for
@@ -59,9 +68,13 @@
@RunWith(AndroidJUnit4.class)
public class InvisibleToggleAccessibilityServiceTargetTest {
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock
private IAccessibilityManager mAccessibilityManagerService;
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
private static final String ALWAYS_ON_SERVICE_PACKAGE_LABEL = "always on a11y service";
private static final String ALWAYS_ON_SERVICE_COMPONENT_NAME =
@@ -104,6 +117,32 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(true);
+
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(false);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -116,6 +155,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
enableA11yService(/* enable= */ false);
addShortcutForA11yService(
@@ -128,6 +168,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -140,6 +181,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(