summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java4
-rw-r--r--core/java/com/android/internal/accessibility/util/AccessibilityUtils.java21
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java20
-rw-r--r--services/core/java/com/android/server/policy/TalkbackShortcutController.java32
-rw-r--r--services/core/java/com/android/server/policy/VoiceAccessShortcutController.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java23
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt14
9 files changed, 167 insertions, 29 deletions
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 66d073fa791e..cb1e0161441f 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -129,6 +129,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82;
public static final int FLAG_CANCELLED = 1;
@@ -225,6 +226,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -807,6 +809,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP";
case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN:
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN";
+ case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 0b1ecf78d28c..d03bb5c3cb17 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -351,4 +352,24 @@ public final class AccessibilityUtils {
}
return result;
}
+
+ /** Returns the {@link ComponentName} of an installed accessibility service by label. */
+ @Nullable
+ public static ComponentName getInstalledAccessibilityServiceComponentNameByLabel(
+ Context context, String label) {
+ AccessibilityManager accessibilityManager =
+ context.getSystemService(AccessibilityManager.class);
+ List<AccessibilityServiceInfo> serviceInfos =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo service : serviceInfos) {
+ final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ if (label.equals(serviceInfo.loadLabel(context.getPackageManager()).toString())
+ && (serviceInfo.applicationInfo.isSystemApp()
+ || serviceInfo.applicationInfo.isUpdatedSystemApp())) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 9f785ac81398..24296406da00 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -19,6 +19,7 @@ package com.android.server.input;
import static android.hardware.input.InputGestureData.createKeyTrigger;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
@@ -240,6 +241,13 @@ final class InputGestureManager {
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
}
+ if (enableVoiceAccessKeyGestures()) {
+ systemShortcuts.add(
+ createKeyGesture(
+ KeyEvent.KEYCODE_V,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ }
if (enableTaskResizingKeyboardShortcuts()) {
systemShortcuts.add(createKeyGesture(
KeyEvent.KEYCODE_LEFT_BRACKET,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 516213b32354..7f511e1e2aa1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -85,15 +85,16 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -502,6 +503,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private TalkbackShortcutController mTalkbackShortcutController;
+ private VoiceAccessShortcutController mVoiceAccessShortcutController;
+
private WindowWakeUpPolicy mWindowWakeUpPolicy;
/**
@@ -2265,6 +2268,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return new TalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new VoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return new WindowWakeUpPolicy(mContext);
}
@@ -2512,6 +2519,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
+ mVoiceAccessShortcutController = injector.getVoiceAccessShortcutController();
mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
@@ -4262,6 +4270,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
.isAccessibilityShortcutAvailable(false);
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
return enableTalkbackAndMagnifierKeyGestures();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return enableVoiceAccessKeyGestures();
default:
return false;
}
@@ -4492,6 +4502,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ if (enableVoiceAccessKeyGestures()) {
+ if (complete) {
+ mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId);
+ }
+ return true;
+ }
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
AppLaunchData data = event.getAppLaunchData();
if (complete && canLaunchApp && data != null
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index 9e16a7d5e83a..efda337527d4 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -18,20 +18,15 @@ package com.android.server.policy;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.List;
import java.util.Set;
/**
@@ -42,7 +37,6 @@ import java.util.Set;
class TalkbackShortcutController {
private static final String TALKBACK_LABEL = "TalkBack";
private final Context mContext;
- private final PackageManager mPackageManager;
public enum ShortcutSource {
GESTURE,
@@ -51,7 +45,6 @@ class TalkbackShortcutController {
TalkbackShortcutController(Context context) {
mContext = context;
- mPackageManager = mContext.getPackageManager();
}
/**
@@ -63,7 +56,10 @@ class TalkbackShortcutController {
boolean toggleTalkback(int userId, ShortcutSource source) {
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
- ComponentName componentName = getTalkbackComponent();
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, TALKBACK_LABEL);
+ ;
if (componentName == null) {
return false;
}
@@ -83,21 +79,6 @@ class TalkbackShortcutController {
return isTalkbackAlreadyEnabled;
}
- private ComponentName getTalkbackComponent() {
- AccessibilityManager accessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- List<AccessibilityServiceInfo> serviceInfos =
- accessibilityManager.getInstalledAccessibilityServiceList();
-
- for (AccessibilityServiceInfo service : serviceInfos) {
- final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
- if (isTalkback(serviceInfo)) {
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
- }
- return null;
- }
-
boolean isTalkBackShortcutGestureEnabled() {
return Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
@@ -120,9 +101,4 @@ class TalkbackShortcutController {
ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
/* serviceEnabled= */ true);
}
-
- private boolean isTalkback(ServiceInfo info) {
- return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString())
- && (info.applicationInfo.isSystemApp() || info.applicationInfo.isUpdatedSystemApp());
- }
}
diff --git a/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
new file mode 100644
index 000000000000..a37fb1140e06
--- /dev/null
+++ b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.internal.accessibility.util.AccessibilityUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/** This class controls voice access shortcut related operations such as toggling, querying. */
+class VoiceAccessShortcutController {
+ private static final String TAG = VoiceAccessShortcutController.class.getSimpleName();
+ private static final String VOICE_ACCESS_LABEL = "Voice Access";
+
+ private final Context mContext;
+
+ VoiceAccessShortcutController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * A function that toggles voice access service.
+ *
+ * @return whether voice access is enabled after being toggled.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean toggleVoiceAccess(int userId) {
+ final Set<ComponentName> enabledServices =
+ AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, VOICE_ACCESS_LABEL);
+ if (componentName == null) {
+ Slog.e(TAG, "Toggle Voice Access failed due to componentName being null");
+ return false;
+ }
+
+ boolean newState = !enabledServices.contains(componentName);
+ AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, newState, userId);
+
+ return newState;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 9d4d94bebfd9..85ef466b2477 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -758,6 +758,18 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES)
+ public void testKeyGestureToggleVoiceAccess() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(true);
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(false);
+ }
+
+ @Test
public void testKeyGestureToggleDoNotDisturb() {
mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_OFF);
Assert.assertTrue(
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6c48ba26a475..4ff3d433632a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -201,6 +201,8 @@ class TestPhoneWindowManager {
private boolean mIsTalkBackEnabled;
private boolean mIsTalkBackShortcutGestureEnabled;
+ private boolean mIsVoiceAccessEnabled;
+
private Intent mBrowserIntent;
private Intent mSmsIntent;
@@ -225,6 +227,18 @@ class TestPhoneWindowManager {
}
}
+ private class TestVoiceAccessShortcutController extends VoiceAccessShortcutController {
+ TestVoiceAccessShortcutController(Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean toggleVoiceAccess(int currentUserId) {
+ mIsVoiceAccessEnabled = !mIsVoiceAccessEnabled;
+ return mIsVoiceAccessEnabled;
+ }
+ }
+
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
super(context, funcs);
@@ -260,6 +274,10 @@ class TestPhoneWindowManager {
return new TestTalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new TestVoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return mWindowWakeUpPolicy;
}
@@ -1024,6 +1042,11 @@ class TestPhoneWindowManager {
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+ void assertVoiceAccess(boolean expectEnabled) {
+ mTestLooper.dispatchAll();
+ Assert.assertEquals(expectEnabled, mIsVoiceAccessEnabled);
+ }
+
void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 4d7085feb98f..d35c9008e8cb 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -830,6 +830,18 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + 'V' -> Toggle Voice Access",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_V
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
+ intArrayOf(KeyEvent.KEYCODE_V),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@@ -843,6 +855,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -861,6 +874,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)