Merge "Implement toggle talkback shortcut" into main
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 2df5418..71d17eb 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -100,6 +100,7 @@
     public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
     public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
     public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_TALKBACK = 63;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -177,6 +178,7 @@
             KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
             KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
             KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+            KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -597,6 +599,8 @@
                 return "KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT";
             case KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
                 return "KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS";
+            case KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
+                return "KEY_GESTURE_TYPE_TOGGLE_TALKBACK";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 2f5236f..bd1625e 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
 
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
@@ -577,6 +578,17 @@
                             focusedToken, /* flags = */0);
                 }
                 break;
+            case KeyEvent.KEYCODE_T:
+                if (keyboardA11yShortcutControl()) {
+                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
             case KeyEvent.KEYCODE_DEL:
                 if (newBugreportKeyboardShortcut()) {
                     if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ad5c840..0b5b0d2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -84,6 +84,7 @@
 import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 
 import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.modifierShortcutDump;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
@@ -1615,7 +1616,8 @@
             case TRIPLE_PRESS_PRIMARY_NOTHING:
                 break;
             case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
-                mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
+                mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+                        TalkbackShortcutController.ShortcutSource.GESTURE);
                 if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
                     performHapticFeedback(HapticFeedbackConstants.CONFIRM,
                             "Stem primary - Triple Press - Toggle Accessibility");
@@ -3603,6 +3605,17 @@
                     return true;
                 }
                 break;
+            case KeyEvent.KEYCODE_T:
+                if (keyboardA11yShortcutControl()) {
+                    if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
+                        mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+                                TalkbackShortcutController.ShortcutSource.KEYBOARD);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK);
+                        return true;
+                    }
+                }
+                break;
             case KeyEvent.KEYCODE_DEL:
                 if (newBugreportKeyboardShortcut()) {
                     if (mEnableBugReportKeyboardShortcut && firstDown
@@ -4034,6 +4047,8 @@
                     case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD:
                         return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController
                                 .isAccessibilityShortcutAvailable(false);
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
+                        return keyboardA11yShortcutControl();
                     default:
                         return false;
                 }
@@ -4251,6 +4266,15 @@
                     mContext.closeSystemDialogs();
                 }
                 return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
+                if (keyboardA11yShortcutControl()) {
+                    if (complete) {
+                        mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+                                TalkbackShortcutController.ShortcutSource.KEYBOARD);
+                    }
+                    return true;
+                }
+                break;
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index e544ae6..9e16a7d 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -44,6 +44,11 @@
     private final Context mContext;
     private final PackageManager mPackageManager;
 
+    public enum ShortcutSource {
+        GESTURE,
+        KEYBOARD,
+    }
+
     TalkbackShortcutController(Context context) {
         mContext = context;
         mPackageManager = mContext.getPackageManager();
@@ -55,7 +60,7 @@
      * @return talkback state after toggle. {@code true} if talkback is enabled, {@code false} if
      * talkback is disabled
      */
-    boolean toggleTalkback(int userId) {
+    boolean toggleTalkback(int userId, ShortcutSource source) {
         final Set<ComponentName> enabledServices =
                 AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
         ComponentName componentName = getTalkbackComponent();
@@ -65,13 +70,13 @@
 
         boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
 
-        if (isTalkBackShortcutGestureEnabled()) {
+        if (source == ShortcutSource.KEYBOARD || isTalkBackShortcutGestureEnabled()) {
             isTalkbackAlreadyEnabled = !isTalkbackAlreadyEnabled;
             AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
-                    isTalkbackAlreadyEnabled);
+                    isTalkbackAlreadyEnabled, userId);
 
             // log stem triple press telemetry if it's a talkback enabled event.
-            if (isTalkbackAlreadyEnabled) {
+            if (source == ShortcutSource.GESTURE && isTalkbackAlreadyEnabled) {
                 logStemTriplePressAccessibilityTelemetry(componentName);
             }
         }
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 cdb4542..d83ffd1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -35,6 +35,7 @@
 
 import androidx.test.filters.MediumTest;
 
+import com.android.hardware.input.Flags;
 import com.android.internal.annotations.Keep;
 
 import junit.framework.Assert;
@@ -393,6 +394,17 @@
                 META_ON | CTRL_ON);
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL)
+    @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    public void testToggleTalkbackPress() {
+        testShortcutInternal("Meta + Alt + T -> Toggle talkback",
+                new int[]{META_KEY, ALT_KEY, KeyEvent.KEYCODE_T},
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
+                KeyEvent.KEYCODE_T,
+                META_ON | ALT_ON);
+    }
+
     private void testShortcutInternal(String testName, int[] testKeys,
             @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
@@ -699,4 +711,16 @@
                 sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS));
         mPhoneWindowManager.assertCloseAllDialogs();
     }
+
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL)
+    public void testKeyGestureToggleTalkback() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+        mPhoneWindowManager.assertTalkBack(true);
+
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+        mPhoneWindowManager.assertTalkBack(false);
+    }
 }
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 a85f866..62670b4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -197,7 +197,7 @@
         }
 
         @Override
-        boolean toggleTalkback(int currentUserId) {
+        boolean toggleTalkback(int currentUserId, ShortcutSource source) {
             mIsTalkBackEnabled = !mIsTalkBackEnabled;
             return mIsTalkBackEnabled;
         }