summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java4
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java31
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java140
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt12
7 files changed, 195 insertions, 1 deletions
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 907f0f2c8fbb..24951c4d516e 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -120,6 +120,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74;
+ public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75;
public static final int FLAG_CANCELLED = 1;
@@ -209,6 +210,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -785,6 +787,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION";
+ case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
+ return "KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK";
default:
return Integer.toHexString(value);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7402a2f24f97..219cefd273ac 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4596,6 +4596,11 @@
exists on the device, the accessibility shortcut will be disabled by default. -->
<string name="config_defaultAccessibilityService" translatable="false"></string>
+ <!-- The component name, flattened to a string, for the default select to speak service to be
+ enabled by the accessibility keyboard shortcut. If the service with the specified component
+ name is not preinstalled then this shortcut will do nothing. -->
+ <string name="config_defaultSelectToSpeakService" translatable="false"></string>
+
<!-- URI for default Accessibility notification sound when to enable accessibility shortcut. -->
<string name="config_defaultAccessibilityNotificationSound" translatable="false"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index db81a3be440f..badb98686fb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3718,6 +3718,7 @@
<java-symbol type="string" name="color_correction_feature_name" />
<java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+ <java-symbol type="string" name="config_defaultSelectToSpeakService" />
<java-symbol type="string" name="config_defaultAccessibilityNotificationSound" />
<java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
<java-symbol type="array" name="config_trustedAccessibilityServices" />
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 22837acde1c5..d4af7b765254 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -522,7 +522,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public boolean isKeyGestureSupported(int gestureType) {
return switch (gestureType) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION -> true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true;
default -> false;
};
}
@@ -685,6 +686,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
targetName = MAGNIFICATION_CONTROLLER_NAME;
break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
+ targetName = mContext.getString(R.string.config_defaultSelectToSpeakService);
+ if (targetName.isEmpty()) {
+ return false;
+ }
+
+ final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName)
+ ? null : ComponentName.unflattenFromString(targetName);
+ AccessibilityServiceInfo accessibilityServiceInfo;
+ synchronized (mLock) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ accessibilityServiceInfo =
+ userState.getInstalledServiceInfoLocked(targetServiceComponent);
+ }
+ if (accessibilityServiceInfo == null) {
+ return false;
+ }
+
+ // Skip enabling if a warning dialog is required for the feature.
+ // TODO(b/377752960): Explore better options to instead show the warning dialog
+ // in this scenario.
+ if (isAccessibilityServiceWarningRequired(accessibilityServiceInfo)) {
+ Slog.w(LOG_TAG,
+ "Accessibility warning is required before this service can be "
+ + "activated automatically via KEY_GESTURE shortcut.");
+ return false;
+ }
+ break;
default:
return false;
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index bc5c2db3881f..6f3540221b63 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -224,6 +224,9 @@ final class InputGestureManager {
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
}
if (keyboardA11yShortcutControl()) {
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
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 945e07913351..d5b930769e43 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -2208,6 +2208,146 @@ public class AccessibilityManagerServiceTest {
verify(mInputFilter).notifyMagnificationShortcutTriggered(anyInt());
}
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_trustedService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo trustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(trustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ trustedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{trustedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(
+ trustedService.getComponentName().flattenToString());
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_preinstalledService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo untrustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(untrustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ untrustedService.getComponentName().flattenToString());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_downloadedService() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo downloadedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ false, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(downloadedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ downloadedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{downloadedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_defaultNotInstalled() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ final AccessibilityServiceInfo defaultService = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ defaultService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{defaultService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_noDefault() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{installedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 40ea9dc9cd89..09a686ca2c3f 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -794,6 +794,18 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + S -> Activate Select to Speak",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_S
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}