Accessibility shortcut improvement (5/n)

- Adds support for magnification and multiple shortcut targets assigned
  to accessibility shortcut key in AccessibilityManagerService.
- New extra field in ACTION_CHOOSE_ACCESSIBILITY_BUTTON intent to
  support accessibility shortcut key.

Bug: 136293963
Test: atest AccessibilityShortcutControllerTest
Test: atest AccessibilityUserStateTest
Test: atest AccessibilityShortcutTest
Change-Id: If0a446dfd269e82ec0d09db92e86f859cdae50d8
diff --git a/api/test-current.txt b/api/test-current.txt
index dacc5d6..ac1c03e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4594,7 +4594,7 @@
 
   public final class AccessibilityManager {
     method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
-    method @Nullable @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public String getAccessibilityShortcutService();
+    method @NonNull @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
     method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void performAccessibilityShortcut();
     method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index f4cadfd..d79740b 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -141,6 +141,16 @@
     }
 
     /**
+     * The {@link ComponentName} of the accessibility shortcut target.
+     *
+     * @return The component name
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
      * The localized summary of the accessibility shortcut target.
      *
      * @return The localized summary if available, and {@code null} if a summary
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index cc28840..843f8e3 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -117,7 +117,7 @@
      * Activity action: Launch UI to manage which accessibility service or feature is assigned
      * to the navigation bar Accessibility button.
      * <p>
-     * Input: Nothing.
+     * Input: {@link #EXTRA_SHORTCUT_TYPE} is the shortcut type.
      * </p>
      * <p>
      * Output: Nothing.
@@ -130,6 +130,42 @@
             "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
 
     /**
+     * Used as an int extra field in {@link #ACTION_CHOOSE_ACCESSIBILITY_BUTTON} intent to specify
+     * the shortcut type.
+     *
+     * @hide
+     */
+    public static final String EXTRA_SHORTCUT_TYPE =
+            "com.android.internal.intent.extra.SHORTCUT_TYPE";
+
+    /**
+     * Used as an int value for {@link #EXTRA_SHORTCUT_TYPE} to represent the accessibility button
+     * shortcut type.
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_BUTTON = 0;
+
+    /**
+     * Used as an int value for {@link #EXTRA_SHORTCUT_TYPE} to represent hardware key shortcut,
+     * such as volume key button.
+     *
+     * @hide
+     */
+    public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
+
+    /**
+     * Annotations for the shortcut type.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            ACCESSIBILITY_BUTTON,
+            ACCESSIBILITY_SHORTCUT_KEY
+    })
+    public @interface ShortcutType {}
+
+    /**
      * Annotations for content flag of UI.
      * @hide
      */
@@ -1242,27 +1278,28 @@
     }
 
     /**
-     * Get the component name of the service currently assigned to the accessibility shortcut.
+     * Returns the list of shortcut target names currently assigned to the given shortcut.
      *
-     * @return The flattened component name
+     * @param shortcutType The shortcut type.
+     * @return The list of shortcut target names.
      * @hide
      */
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
-    @Nullable
-    public String getAccessibilityShortcutService() {
+    @NonNull
+    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
         }
         if (service != null) {
             try {
-                return service.getAccessibilityShortcutService();
+                return service.getAccessibilityShortcutTargets(shortcutType);
             } catch (RemoteException re) {
                 re.rethrowFromSystemServer();
             }
         }
-        return null;
+        return Collections.emptyList();
     }
 
     /**
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 023fda5..36515b3 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -73,7 +73,7 @@
     void performAccessibilityShortcut();
 
     // Requires Manifest.permission.MANAGE_ACCESSIBILITY
-    String getAccessibilityShortcutService();
+    List<String> getAccessibilityShortcutTargets(int shortcutType);
 
     // System process only
     boolean sendFingerprintGesture(int gestureKeyCode);
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 0b15cd0..3fdedc8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -17,6 +17,7 @@
 package com.android.internal.accessibility;
 
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
@@ -34,6 +35,7 @@
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -52,11 +54,12 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
 /**
- * Class to help manage the accessibility shortcut
+ * Class to help manage the accessibility shortcut key
  */
 public class AccessibilityShortcutController {
     private static final String TAG = "AccessibilityShortcutController";
@@ -66,6 +69,8 @@
             new ComponentName("com.android.server.accessibility", "ColorInversion");
     public static final ComponentName DALTONIZER_COMPONENT_NAME =
             new ComponentName("com.android.server.accessibility", "Daltonizer");
+    public static final String MAGNIFICATION_CONTROLLER_NAME =
+            "com.android.server.accessibility.MagnificationController";
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -84,26 +89,6 @@
     public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
 
     /**
-     * Get the component name string for the service or feature currently assigned to the
-     * accessiblity shortcut
-     *
-     * @param context A valid context
-     * @param userId The user ID of interest
-     * @return The flattened component name string of the service selected by the user, or the
-     *         string for the default service if the user has not made a selection
-     */
-    public static String getTargetServiceComponentNameString(
-            Context context, int userId) {
-        final String currentShortcutServiceId = Settings.Secure.getStringForUser(
-                context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                userId);
-        if (currentShortcutServiceId != null) {
-            return currentShortcutServiceId;
-        }
-        return context.getString(R.string.config_defaultAccessibilityService);
-    }
-
-    /**
      * @return An immutable map from dummy component names to feature info for toggling a framework
      *         feature
      */
@@ -163,7 +148,7 @@
     /**
      * Check if the shortcut is available.
      *
-     * @param onLockScreen Whether or not the phone is currently locked.
+     * @param phoneLocked Whether or not the phone is currently locked.
      *
      * @return {@code true} if the shortcut is available
      */
@@ -172,8 +157,7 @@
     }
 
     public void onSettingsChanged() {
-        final boolean haveValidService =
-                !TextUtils.isEmpty(getTargetServiceComponentNameString(mContext, mUserId));
+        final boolean hasShortcutTarget = hasShortcutTarget();
         final ContentResolver cr = mContext.getContentResolver();
         final boolean enabled = Settings.Secure.getIntForUser(
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
@@ -183,7 +167,7 @@
         mEnabledOnLockScreen = Settings.Secure.getIntForUser(
                 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
                 dialogAlreadyShown, mUserId) == 1;
-        mIsShortcutEnabled = enabled && haveValidService;
+        mIsShortcutEnabled = enabled && hasShortcutTarget;
     }
 
     /**
@@ -205,7 +189,6 @@
             vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
         }
 
-
         if (dialogAlreadyShown == 0) {
             // The first time, we show a warning rather than toggle the service to give the user a
             // chance to turn off this feature before stuff gets enabled.
@@ -229,32 +212,44 @@
                 mAlertDialog.dismiss();
                 mAlertDialog = null;
             }
-
-            // Show a toast alerting the user to what's happening
-            final String serviceName = getShortcutFeatureDescription(false /* no summary */);
-            if (serviceName == null) {
-                Slog.e(TAG, "Accessibility shortcut set to invalid service");
-                return;
-            }
-            // For accessibility services, show a toast explaining what we're doing.
-            final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
-            if (serviceInfo != null) {
-                String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
-                        ? R.string.accessibility_shortcut_disabling_service
-                        : R.string.accessibility_shortcut_enabling_service);
-                String toastMessage = String.format(toastMessageFormatString, serviceName);
-                Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
-                        mContext, toastMessage, Toast.LENGTH_LONG);
-                warningToast.getWindowParams().privateFlags |=
-                        WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-                warningToast.show();
-            }
-
+            showToast();
             mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext)
                     .performAccessibilityShortcut();
         }
     }
 
+    /**
+     * Show toast if current assigned shortcut target is an accessibility service and its target
+     * sdk version is less than or equal to Q, or greater than Q and does not request
+     * accessibility button.
+     */
+    private void showToast() {
+        final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
+        if (serviceInfo == null) {
+            return;
+        }
+        final String serviceName = getShortcutFeatureDescription(/* no summary */ false);
+        if (serviceName == null) {
+            return;
+        }
+        final boolean requestA11yButton = (serviceInfo.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+                .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton) {
+            return;
+        }
+        // For accessibility services, show a toast explaining what we're doing.
+        String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
+                ? R.string.accessibility_shortcut_disabling_service
+                : R.string.accessibility_shortcut_enabling_service);
+        String toastMessage = String.format(toastMessageFormatString, serviceName);
+        Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
+                mContext, toastMessage, Toast.LENGTH_LONG);
+        warningToast.getWindowParams().privateFlags |=
+                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        warningToast.show();
+    }
+
     private AlertDialog createShortcutWarningDialog(int userId) {
         final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
 
@@ -288,25 +283,21 @@
     }
 
     private AccessibilityServiceInfo getInfoForTargetService() {
-        final String currentShortcutServiceString = getTargetServiceComponentNameString(
-                mContext, UserHandle.USER_CURRENT);
-        if (currentShortcutServiceString == null) {
+        final ComponentName targetComponentName = getShortcutTargetComponentName();
+        if (targetComponentName == null) {
             return null;
         }
         AccessibilityManager accessibilityManager =
                 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
         return accessibilityManager.getInstalledServiceInfoWithComponentName(
-                        ComponentName.unflattenFromString(currentShortcutServiceString));
+                targetComponentName);
     }
 
     private String getShortcutFeatureDescription(boolean includeSummary) {
-        final String currentShortcutServiceString = getTargetServiceComponentNameString(
-                mContext, UserHandle.USER_CURRENT);
-        if (currentShortcutServiceString == null) {
+        final ComponentName targetComponentName = getShortcutTargetComponentName();
+        if (targetComponentName == null) {
             return null;
         }
-        final ComponentName targetComponentName =
-                ComponentName.unflattenFromString(currentShortcutServiceString);
         final ToggleableFrameworkFeatureInfo frameworkFeatureInfo =
                 getFrameworkShortcutFeaturesMap().get(targetComponentName);
         if (frameworkFeatureInfo != null) {
@@ -372,6 +363,36 @@
     }
 
     /**
+     * Returns {@code true} if any shortcut targets were assigned to accessibility shortcut key.
+     */
+    private boolean hasShortcutTarget() {
+        // AccessibilityShortcutController is initialized earlier than AccessibilityManagerService.
+        // AccessibilityManager#getAccessibilityShortcutTargets may not return correct shortcut
+        // targets during boot. Needs to read settings directly here.
+        String shortcutTargets = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, mUserId);
+        if (TextUtils.isEmpty(shortcutTargets)) {
+            shortcutTargets = mContext.getString(R.string.config_defaultAccessibilityService);
+        }
+        return !TextUtils.isEmpty(shortcutTargets);
+    }
+
+    /**
+     * Gets the component name of the shortcut target.
+     *
+     * @return The component name, or null if it's assigned by multiple targets.
+     */
+    private ComponentName getShortcutTargetComponentName() {
+        final List<String> shortcutTargets = mFrameworkObjectProvider
+                .getAccessibilityManagerInstance(mContext)
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
+        if (shortcutTargets.size() != 1) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(shortcutTargets.get(0));
+    }
+
+    /**
      * Class to wrap TextToSpeech for shortcut dialog spoken feedback.
      */
     private class TtsPrompt implements TextToSpeech.OnInitListener {
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 7b405434..82854e5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -20,6 +20,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -49,8 +50,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.media.Ringtone;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
@@ -157,9 +160,12 @@
         when(mFrameworkObjectProvider.getRingtone(eq(mContext), any())).thenReturn(mRingtone);
 
         when(mResources.getString(anyInt())).thenReturn("Howdy %s");
+        when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(null);
         when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
 
         ResolveInfo resolveInfo = mock(ResolveInfo.class);
+        resolveInfo.serviceInfo = mock(ServiceInfo.class);
+        resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
         when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
         when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
         when(mServiceInfo.getComponentName())
@@ -200,42 +206,47 @@
     }
 
     @Test
-    public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
+    public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse()
+            throws Exception {
         configureNoShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         assertFalse(getController().isAccessibilityShortcutAvailable(false));
     }
 
     @Test
-    public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
+    public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue()
+            throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         assertTrue(getController().isAccessibilityShortcutAvailable(false));
     }
 
     @Test
-    public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
+    public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse()
+            throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
         assertFalse(getController().isAccessibilityShortcutAvailable(false));
     }
 
     @Test
-    public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
+    public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse()
+            throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         assertFalse(getController().isAccessibilityShortcutAvailable(true));
     }
 
     @Test
-    public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
+    public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue()
+            throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
         assertTrue(getController().isAccessibilityShortcutAvailable(true));
     }
 
     @Test
-    public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() {
+    public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() throws Exception {
         // When the user hasn't specified a lock screen preference, we allow from the lock screen
         // as long as the user has agreed to enable the shortcut
         configureValidShortcutService();
@@ -249,17 +260,19 @@
     }
 
     @Test
-    public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
+    public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
+        configureNoShortcutService();
         accessibilityShortcutController.onSettingsChanged();
         assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
     }
 
     @Test
-    public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
+    public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureNoShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -269,7 +282,8 @@
     }
 
     @Test
-    public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
+    public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -279,7 +293,8 @@
     }
 
     @Test
-    public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
+    public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue()
+            throws Exception {
         configureShortcutEnabled(DISABLED);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -289,7 +304,8 @@
     }
 
     @Test
-    public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
+    public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse()
+            throws Exception {
         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -299,7 +315,8 @@
     }
 
     @Test
-    public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
+    public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -370,7 +387,7 @@
     }
 
     @Test
-    public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
+    public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
@@ -458,7 +475,22 @@
     }
 
     @Test
-    public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt() {
+    public void testOnAccessibilityShortcut_sdkGreaterThanQ_reqA11yButton_callsServiceWithNoToast()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureValidShortcutService();
+        configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
+        configureRequestAccessibilityButton();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        getController().performAccessibilityShortcut();
+
+        verifyZeroInteractions(mToast);
+        verify(mAccessibilityManagerService).performAccessibilityShortcut();
+    }
+
+    @Test
+    public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         configureTtsSpokenPromptEnabled();
@@ -482,7 +514,8 @@
     }
 
     @Test
-    public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt() {
+    public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt()
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         configureTtsSpokenPromptEnabled();
@@ -500,19 +533,28 @@
         verify(mRingtone).play();
     }
 
-    private void configureNoShortcutService() {
+    private void configureNoShortcutService() throws Exception {
+        when(mAccessibilityManagerService
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .thenReturn(Collections.emptyList());
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
     }
 
-    private void configureValidShortcutService() {
+    private void configureValidShortcutService() throws Exception {
+        when(mAccessibilityManagerService
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
         Settings.Secure.putString(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
     }
 
-    private void configureFirstFrameworkFeature() {
+    private void configureFirstFrameworkFeature() throws Exception {
         ComponentName featureComponentName =
                 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
                         .keySet().toArray()[0];
+        when(mAccessibilityManagerService
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                 featureComponentName.flattenToString());
     }
@@ -552,6 +594,15 @@
                 .FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK;
     }
 
+    private void configureRequestAccessibilityButton() {
+        mServiceInfo.flags |= AccessibilityServiceInfo
+                .FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+    }
+
+    private void configureApplicationTargetSdkVersion(int versionCode) {
+        mApplicationInfo.targetSdkVersion = versionCode;
+    }
+
     private void configureHandlerCallbackInvocation() {
         doAnswer((InvocationOnMock invocation) -> {
             Message m = (Message) invocation.getArguments()[0];
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7fdd83b..6a6e2b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,11 @@
 
 package com.android.server.accessibility;
 
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -31,6 +36,7 @@
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManagerInternal;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -70,6 +76,7 @@
 import android.provider.SettingsStringUtil.SettingStringHelper;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -113,9 +120,9 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * This class is instantiated by the system as a system level service and can be
@@ -754,6 +761,8 @@
             userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
             userState.setDisplayMagnificationEnabledLocked(false);
             userState.setNavBarMagnificationEnabledLocked(false);
+            userState.disableShortcutMagnificationLocked();
+
             userState.setAutoclickEnabledLocked(false);
             userState.mEnabledServices.clear();
             userState.mEnabledServices.add(service);
@@ -1072,6 +1081,8 @@
         }
     }
 
+    // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
+    //  ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
     private void notifyAccessibilityButtonClickedLocked(int displayId) {
         final AccessibilityUserState state = getCurrentUserStateLocked();
 
@@ -1105,8 +1116,8 @@
             if (state.getServiceAssignedToAccessibilityButtonLocked() == null
                     && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 mMainHandler.sendMessage(obtainMessage(
-                        AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
-                        displayId));
+                        AccessibilityManagerService::showAccessibilityTargetsSelection, this,
+                        displayId, ACCESSIBILITY_BUTTON));
             } else if (state.isNavBarMagnificationEnabledLocked()
                     && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
                 mMainHandler.sendMessage(obtainMessage(
@@ -1125,8 +1136,8 @@
             }
             // The user may have turned off the assigned service or feature
             mMainHandler.sendMessage(obtainMessage(
-                    AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
-                    displayId));
+                    AccessibilityManagerService::showAccessibilityTargetsSelection, this,
+                    displayId, ACCESSIBILITY_BUTTON));
         }
     }
 
@@ -1138,13 +1149,27 @@
         }
     }
 
-    private void showAccessibilityButtonTargetSelection(int displayId) {
+    private void showAccessibilityTargetsSelection(int displayId,
+            @ShortcutType int shortcutType) {
         Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+        bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
         mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
     }
 
+    private void launchShortcutTargetActivity(int displayId, ComponentName name) {
+        final Intent intent = new Intent();
+        final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+        intent.setComponent(name);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        try {
+            mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
+        } catch (ActivityNotFoundException ignore) {
+            // ignore the exception
+        }
+    }
+
     private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
         final AccessibilityUserState state = getCurrentUserStateLocked();
         mIsAccessibilityButtonShown = available;
@@ -1353,9 +1378,8 @@
      */
     private void readComponentNamesFromSettingLocked(String settingName, int userId,
             Set<ComponentName> outComponentNames) {
-        String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                settingName, userId);
-        readComponentNamesFromStringLocked(settingValue, outComponentNames, false);
+        readColonDelimitedSettingToSet(settingName, userId, outComponentNames,
+                str -> ComponentName.unflattenFromString(str));
     }
 
     /**
@@ -1370,34 +1394,57 @@
     private void readComponentNamesFromStringLocked(String names,
             Set<ComponentName> outComponentNames,
             boolean doMerge) {
-        if (!doMerge) {
-            outComponentNames.clear();
-        }
-        if (names != null) {
-            TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
-            splitter.setString(names);
-            while (splitter.hasNext()) {
-                String str = splitter.next();
-                if (str == null || str.length() <= 0) {
-                    continue;
-                }
-                ComponentName enabledService = ComponentName.unflattenFromString(str);
-                if (enabledService != null) {
-                    outComponentNames.add(enabledService);
-                }
-            }
-        }
+        readColonDelimitedStringToSet(names, outComponentNames, doMerge,
+                str -> ComponentName.unflattenFromString(str));
     }
 
     @Override
     public void persistComponentNamesToSettingLocked(String settingName,
             Set<ComponentName> componentNames, int userId) {
-        StringBuilder builder = new StringBuilder();
-        for (ComponentName componentName : componentNames) {
+        persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames,
+                componentName -> componentName.flattenToShortString());
+    }
+
+    private <T> void readColonDelimitedSettingToSet(String settingName, int userId, Set<T> outSet,
+            Function<String, T> toItem) {
+        final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                settingName, userId);
+        readColonDelimitedStringToSet(settingValue, outSet, false, toItem);
+    }
+
+    private <T> void readColonDelimitedStringToSet(String names, Set<T> outSet, boolean doMerge,
+            Function<String, T> toItem) {
+        if (!doMerge) {
+            outSet.clear();
+        }
+        if (!TextUtils.isEmpty(names)) {
+            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+            splitter.setString(names);
+            while (splitter.hasNext()) {
+                final String str = splitter.next();
+                if (TextUtils.isEmpty(str)) {
+                    continue;
+                }
+                final T item = toItem.apply(str);
+                if (item != null) {
+                    outSet.add(item);
+                }
+            }
+        }
+    }
+
+    private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
+            Set<T> set, Function<T, String> toString) {
+        final StringBuilder builder = new StringBuilder();
+        for (T item : set) {
+            final String str = (item != null ? toString.apply(item) : null);
+            if (TextUtils.isEmpty(str)) {
+                continue;
+            }
             if (builder.length() > 0) {
                 builder.append(COMPONENT_NAME_SEPARATOR);
             }
-            builder.append(componentName.flattenToShortString());
+            builder.append(str);
         }
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -1537,7 +1584,8 @@
             if (userState.isDisplayMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
             }
-            if (userState.isNavBarMagnificationEnabledLocked()) {
+            if (userState.isNavBarMagnificationEnabledLocked()
+                    || userState.isShortcutKeyMagnificationEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
             }
             if (userHasMagnificationServicesLocked(userState)) {
@@ -1647,7 +1695,6 @@
         mInitialized = true;
         updateLegacyCapabilitiesLocked(userState);
         updateServicesLocked(userState);
-        updateAccessibilityShortcutLocked(userState);
         updateWindowsForAccessibilityCallbackLocked(userState);
         updateFilterKeyEventsLocked(userState);
         updateTouchExplorationLocked(userState);
@@ -1657,6 +1704,7 @@
         scheduleUpdateInputFilter(userState);
         updateRelevantEventsLocked(userState);
         scheduleUpdateClientsIfNeededLocked(userState);
+        updateAccessibilityShortcutKeyTargetsLocked(userState);
         updateAccessibilityButtonTargetsLocked(userState);
     }
 
@@ -1751,7 +1799,7 @@
         somethingChanged |= readHighTextContrastEnabledSettingLocked(userState);
         somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
         somethingChanged |= readAutoclickEnabledSettingLocked(userState);
-        somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
+        somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
         somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
         somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
         return somethingChanged;
@@ -1847,57 +1895,43 @@
         }
     }
 
-    private boolean readAccessibilityShortcutSettingLocked(AccessibilityUserState userState) {
-        String componentNameToEnableString = AccessibilityShortcutController
-                .getTargetServiceComponentNameString(mContext, userState.mUserId);
-        if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
-            if (userState.getServiceToEnableWithShortcutLocked() == null) {
-                return false;
+    private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                userState.mUserId, targetsFromSetting, str -> str);
+        if (targetsFromSetting.isEmpty()) {
+            // Fall back to device's default a11y service.
+            final String defaultService = mContext.getString(
+                    R.string.config_defaultAccessibilityService);
+            if (!TextUtils.isEmpty(defaultService)) {
+                targetsFromSetting.add(defaultService);
             }
-            userState.setServiceToEnableWithShortcutLocked(null);
-            return true;
-        }
-        ComponentName componentNameToEnable =
-            ComponentName.unflattenFromString(componentNameToEnableString);
-        if ((componentNameToEnable != null)
-                && componentNameToEnable.equals(userState.getServiceToEnableWithShortcutLocked())) {
-            return false;
         }
 
-        userState.setServiceToEnableWithShortcutLocked(componentNameToEnable);
+        final Set<String> currentTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+        if (targetsFromSetting.equals(currentTargets)) {
+            return false;
+        }
+        currentTargets.clear();
+        currentTargets.addAll(targetsFromSetting);
         scheduleNotifyClientsOfServicesStateChangeLocked(userState);
         return true;
     }
 
     private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
-        String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
-        if (TextUtils.isEmpty(componentId)) {
-            if ((userState.getServiceAssignedToAccessibilityButtonLocked() == null)
-                    && !userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                return false;
-            }
-            userState.setServiceAssignedToAccessibilityButtonLocked(null);
-            userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
-            return true;
-        }
+        final Set<String> targetsFromSetting = new ArraySet<>();
+        readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                userState.mUserId, targetsFromSetting, str -> str);
 
-        if (componentId.equals(MagnificationController.class.getName())) {
-            if (userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
-                return false;
-            }
-            userState.setServiceAssignedToAccessibilityButtonLocked(null);
-            userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
-            return true;
-        }
-
-        ComponentName componentName = ComponentName.unflattenFromString(componentId);
-        if (Objects.equals(componentName,
-                userState.getServiceAssignedToAccessibilityButtonLocked())) {
+        final Set<String> currentTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+        if (targetsFromSetting.equals(currentTargets)) {
             return false;
         }
-        userState.setServiceAssignedToAccessibilityButtonLocked(componentName);
-        userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
+        currentTargets.clear();
+        currentTargets.addAll(targetsFromSetting);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
         return true;
     }
 
@@ -1921,34 +1955,33 @@
     }
 
     /**
-     * Check if the service that will be enabled by the shortcut is installed. If it isn't,
-     * clear the value and the associated setting so a sideloaded service can't spoof the
-     * package name of the default service.
-     *
-     * @param userState
+     * Check if the targets that will be enabled by the accessibility shortcut key is installed.
+     * If it isn't, remove it from the list and associated setting so a side loaded service can't
+     * spoof the package name of the default service.
      */
-    private void updateAccessibilityShortcutLocked(AccessibilityUserState userState) {
-        if (userState.getServiceToEnableWithShortcutLocked() == null) {
+    private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
+        final Set<String> currentTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+        final int lastSize = currentTargets.size();
+        if (lastSize == 0) {
             return;
         }
-        boolean shortcutServiceIsInstalled =
-                AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
-                        .containsKey(userState.getServiceToEnableWithShortcutLocked());
-        for (int i = 0; !shortcutServiceIsInstalled && (i < userState.mInstalledServices.size());
-                i++) {
-            if (userState.mInstalledServices.get(i).getComponentName()
-                    .equals(userState.getServiceToEnableWithShortcutLocked())) {
-                shortcutServiceIsInstalled = true;
-            }
+        currentTargets.removeIf(
+                name -> !userState.isShortcutTargetInstalledLocked(name));
+        if (lastSize == currentTargets.size()) {
+            return;
         }
-        if (!shortcutServiceIsInstalled) {
-            userState.setServiceToEnableWithShortcutLocked(null);
+
+        // Update setting key with new value.
+        persistColonDelimitedSetToSettingLocked(
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                userState.mUserId, currentTargets, str -> str);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+
+        // Disable accessibility shortcut key if there's no shortcut installed.
+        if (currentTargets.isEmpty()) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null,
-                        userState.mUserId);
-
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId);
             } finally {
@@ -2004,7 +2037,8 @@
         // displays in one display. It's not a real display and there's no input events for it.
         final ArrayList<Display> displays = getValidDisplayList();
         if (userState.isDisplayMagnificationEnabledLocked()
-                || userState.isNavBarMagnificationEnabledLocked()) {
+                || userState.isNavBarMagnificationEnabledLocked()
+                || userState.isShortcutKeyMagnificationEnabledLocked()) {
             for (int i = 0; i < displays.size(); i++) {
                 final Display display = displays.get(i);
                 getMagnificationController().register(display.getDisplayId());
@@ -2088,7 +2122,14 @@
         }
     }
 
+    /**
+     * 1) Update accessibility button availability to accessibility services.
+     * 2) Check if the targets that will be enabled by the accessibility button is installed.
+     *    If it isn't, remove it from the list and associated setting so a side loaded service can't
+     *    spoof the package name of the default service.
+     */
     private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
+        // Update accessibility button availability.
         for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
             final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
             if (service.mRequestAccessibilityButton) {
@@ -2096,6 +2137,24 @@
                         service.isAccessibilityButtonAvailableLocked(userState));
             }
         }
+
+        final Set<String> currentTargets =
+                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+        final int lastSize = currentTargets.size();
+        if (lastSize == 0) {
+            return;
+        }
+        currentTargets.removeIf(
+                name -> !userState.isShortcutTargetInstalledLocked(name));
+        if (lastSize == currentTargets.size()) {
+            return;
+        }
+
+        // Update setting key with new value.
+        persistColonDelimitedSetToSettingLocked(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                userState.mUserId, currentTargets, str -> str);
+        scheduleNotifyClientsOfServicesStateChangeLocked(userState);
     }
 
     private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
@@ -2156,7 +2215,7 @@
     }
 
     /**
-     * AIDL-exposed method to be called when the accessibility shortcut is enabled. Requires
+     * AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
      * permission to write secure settings, since someone with that permission can enable
      * accessibility services themselves.
      */
@@ -2168,52 +2227,177 @@
             throw new SecurityException(
                     "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission");
         }
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+    }
+
+    /**
+     * Perform the accessibility shortcut action.
+     *
+     * @param shortcutType The shortcut type.
+     * @param displayId The display id of the accessibility button.
+     */
+    private void performAccessibilityShortcutInternal(int displayId,
+            @ShortcutType int shortcutType) {
+        final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
+        if (shortcutTargets.isEmpty()) {
+            Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
+            return;
+        }
+        // In case there are many targets assigned to the given shortcut.
+        if (shortcutTargets.size() > 1) {
+            showAccessibilityTargetsSelection(displayId, shortcutType);
+            return;
+        }
+        final String targetName = shortcutTargets.get(0);
+        // In case user assigned magnification to the given shortcut.
+        if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
+            sendAccessibilityButtonToInputFilter(displayId);
+            return;
+        }
+        final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName);
+        if (targetComponentName == null) {
+            Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+            return;
+        }
+        // In case user assigned an accessibility framework feature to the given shortcut.
+        if (performAccessibilityFrameworkFeature(targetComponentName)) {
+            return;
+        }
+        // In case user assigned an accessibility shortcut target to the given shortcut.
+        if (performAccessibilityShortcutTargetActivity(displayId, targetComponentName)) {
+            return;
+        }
+        // in case user assigned an accessibility service to the given shortcut.
+        if (performAccessibilityShortcutTargetService(
+                displayId, shortcutType, targetComponentName)) {
+            return;
+        }
+    }
+
+    private boolean performAccessibilityFrameworkFeature(ComponentName assignedTarget) {
         final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
-        synchronized(mLock) {
-            final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
-            final ComponentName serviceName = userState.getServiceToEnableWithShortcutLocked();
-            if (serviceName == null) {
-                return;
-            }
-            if (frameworkFeatureMap.containsKey(serviceName)) {
-                // Toggle the requested framework feature
-                ToggleableFrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(serviceName);
-                SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
-                        featureInfo.getSettingKey(), mCurrentUserId);
-                // Assuming that the default state will be to have the feature off
-                if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
-                    setting.write(featureInfo.getSettingOnValue());
-                } else {
-                    setting.write(featureInfo.getSettingOffValue());
+        if (!frameworkFeatureMap.containsKey(assignedTarget)) {
+            return false;
+        }
+        // Toggle the requested framework feature
+        final ToggleableFrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget);
+        final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
+                featureInfo.getSettingKey(), mCurrentUserId);
+        // Assuming that the default state will be to have the feature off
+        if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
+            setting.write(featureInfo.getSettingOnValue());
+        } else {
+            setting.write(featureInfo.getSettingOffValue());
+        }
+        return true;
+    }
+
+    private boolean performAccessibilityShortcutTargetActivity(int displayId,
+            ComponentName assignedTarget) {
+        synchronized (mLock) {
+            final AccessibilityUserState userState = getCurrentUserStateLocked();
+            for (int i = 0; i < userState.mInstalledShortcuts.size(); i++) {
+                final AccessibilityShortcutInfo shortcutInfo = userState.mInstalledShortcuts.get(i);
+                if (!shortcutInfo.getComponentName().equals(assignedTarget)) {
+                    continue;
                 }
-            }
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (userState.mComponentNameToServiceMap.get(serviceName) == null) {
-                    enableAccessibilityServiceLocked(serviceName, mCurrentUserId);
-                } else {
-                    disableAccessibilityServiceLocked(serviceName, mCurrentUserId);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                launchShortcutTargetActivity(displayId, assignedTarget);
+                return true;
             }
         }
-    };
+        return false;
+    }
+
+    /**
+     * Perform accessibility service shortcut action.
+     *
+     * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
+     *    version <= Q: callbacks to accessibility service if service is bounded and requests
+     *    accessibility button.
+     * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+     *    version <= Q: turns on / off the accessibility service.
+     * 3) For services targeting sdk version > Q:
+     *    a) Turns on / off the accessibility service, if service does not request accessibility
+     *       button.
+     *    b) Callbacks to accessibility service if service is bounded and requests accessibility
+     *       button.
+     */
+    private boolean performAccessibilityShortcutTargetService(int displayId,
+            @ShortcutType int shortcutType, ComponentName assignedTarget) {
+        synchronized (mLock) {
+            final AccessibilityUserState userState = getCurrentUserStateLocked();
+            final AccessibilityServiceInfo installedServiceInfo =
+                    userState.getInstalledServiceInfoLocked(assignedTarget);
+            if (installedServiceInfo == null) {
+                Slog.d(LOG_TAG, "Perform shortcut failed, invalid component name:"
+                        + assignedTarget);
+                return false;
+            }
+
+            final AccessibilityServiceConnection serviceConnection =
+                    userState.getServiceConnectionLocked(assignedTarget);
+            final int targetSdk = installedServiceInfo.getResolveInfo()
+                    .serviceInfo.applicationInfo.targetSdkVersion;
+            final boolean requestA11yButton = (installedServiceInfo.flags
+                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+            // Turns on / off the accessibility service
+            if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+                    || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
+                if (serviceConnection == null) {
+                    enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
+                } else {
+                    disableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
+                }
+                return true;
+            }
+            // Callbacks to a11y service if it's bounded and requests a11y button.
+            if (serviceConnection == null
+                    || !userState.mBoundServices.contains(serviceConnection)
+                    || !serviceConnection.mRequestAccessibilityButton) {
+                Slog.d(LOG_TAG, "Perform shortcut failed, service is not ready:"
+                        + assignedTarget);
+                return false;
+            }
+            serviceConnection.notifyAccessibilityButtonClickedLocked(displayId);
+            return true;
+        }
+    }
 
     @Override
-    public String getAccessibilityShortcutService() {
-        if (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException(
                     "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
         }
-        synchronized(mLock) {
-            final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
-            if (userState.getServiceToEnableWithShortcutLocked() == null) {
-                return null;
+        return getAccessibilityShortcutTargetsInternal(shortcutType);
+    }
+
+    private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
+        synchronized (mLock) {
+            final AccessibilityUserState userState = getCurrentUserStateLocked();
+            final ArrayList<String> shortcutTargets = new ArrayList<>(
+                    userState.getShortcutTargetsLocked(shortcutType));
+            if (shortcutType != ACCESSIBILITY_BUTTON) {
+                return shortcutTargets;
             }
-            return userState.getServiceToEnableWithShortcutLocked().flattenToString();
+            // Adds legacy a11y services requesting a11y button into the list.
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if (!service.mRequestAccessibilityButton
+                        || service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo
+                        .targetSdkVersion > Build.VERSION_CODES.Q) {
+                    continue;
+                }
+                final String serviceName = service.getComponentName().flattenToString();
+                if (!TextUtils.isEmpty(serviceName)) {
+                    shortcutTargets.add(serviceName);
+                }
+            }
+            return shortcutTargets;
         }
     }
 
@@ -2609,6 +2793,8 @@
         private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
 
+        // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
+        //  Setting provider after new shortcut UI merged.
         private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
 
@@ -2713,7 +2899,7 @@
                         || mShowImeWithHardKeyboardUri.equals(uri)) {
                     userState.reconcileSoftKeyboardModeWithSettingsLocked();
                 } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) {
-                    if (readAccessibilityShortcutSettingLocked(userState)) {
+                    if (readAccessibilityShortcutKeySettingLocked(userState)) {
                         onUserStateChangedLocked(userState);
                     }
                 } else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
@@ -2724,8 +2910,6 @@
                         || mUserInteractiveUiTimeoutUri.equals(uri)) {
                     readUserRecommendedUiTimeoutSettingsLocked(userState);
                 }
-                // TODO(a11y shortcut): Monitor new setting keys, when user adds shortcut, and
-                //  remove from the list of enabled targets anything that's been uninstalled.
             }
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 6cadb6d..cbff6bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -294,6 +294,7 @@
         }
     }
 
+    // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
     public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
         // If the service does not request the accessibility button, it isn't available
         if (!mRequestAccessibilityButton) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a0b9866..a163f74 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -22,6 +22,11 @@
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 
 import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -33,10 +38,14 @@
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.accessibility.AccessibilityShortcutController;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -79,19 +88,18 @@
 
     final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
 
+    final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
+
+    final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+
     private final ServiceInfoChangeListener mServiceInfoChangeListener;
 
-    private ComponentName mServiceAssignedToAccessibilityButton;
-
     private ComponentName mServiceChangingSoftKeyboardMode;
 
-    private ComponentName mServiceToEnableWithShortcut;
-
     private boolean mBindInstantServiceAllowed;
     private boolean mIsAutoclickEnabled;
     private boolean mIsDisplayMagnificationEnabled;
     private boolean mIsFilterKeyEventsEnabled;
-    private boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
     private boolean mIsNavBarMagnificationEnabled;
     private boolean mIsPerformGesturesEnabled;
     private boolean mIsTextHighContrastEnabled;
@@ -141,11 +149,11 @@
         // Clear state persisted in settings.
         mEnabledServices.clear();
         mTouchExplorationGrantedServices.clear();
+        mAccessibilityShortcutKeyTargets.clear();
+        mAccessibilityButtonTargets.clear();
         mIsTouchExplorationEnabled = false;
         mIsDisplayMagnificationEnabled = false;
         mIsNavBarMagnificationEnabled = false;
-        mServiceAssignedToAccessibilityButton = null;
-        mIsNavBarMagnificationAssignedToAccessibilityButton = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
         mUserInteractiveUiTimeout = 0;
@@ -435,6 +443,26 @@
         pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size()));
         pw.append("}");
         pw.println();
+        pw.append("     shortcut key:{");
+        int size = mAccessibilityShortcutKeyTargets.size();
+        for (int i = 0; i < size; i++) {
+            final String componentId = mAccessibilityShortcutKeyTargets.valueAt(i);
+            pw.append(componentId);
+            if (i + 1 < size) {
+                pw.append(", ");
+            }
+        }
+        pw.println("}");
+        pw.append("     button:{");
+        size = mAccessibilityButtonTargets.size();
+        for (int i = 0; i < size; i++) {
+            final String componentId = mAccessibilityButtonTargets.valueAt(i);
+            pw.append(componentId);
+            if (i + 1 < size) {
+                pw.append(", ");
+            }
+        }
+        pw.println("}");
         pw.append("     Bound services:{");
         final int serviceCount = mBoundServices.size();
         for (int j = 0; j < serviceCount; j++) {
@@ -525,20 +553,85 @@
         mLastSentClientState = state;
     }
 
-    public boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
-        return mIsNavBarMagnificationAssignedToAccessibilityButton;
+    public boolean isShortcutKeyMagnificationEnabledLocked() {
+        return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
     }
 
-    public void setNavBarMagnificationAssignedToAccessibilityButtonLocked(boolean assigned) {
-        mIsNavBarMagnificationAssignedToAccessibilityButton = assigned;
+    /**
+     * Disable both shortcuts' magnification function.
+     */
+    public void disableShortcutMagnificationLocked() {
+        mAccessibilityShortcutKeyTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
+        mAccessibilityButtonTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
     }
 
-    public boolean isNavBarMagnificationEnabledLocked() {
-        return mIsNavBarMagnificationEnabled;
+    /**
+     * Returns a set which contains the flattened component names and the system class names
+     * assigned to the given shortcut.
+     *
+     * @param shortcutType The shortcut type.
+     * @return The array set of the strings
+     */
+    public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
+        if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            return mAccessibilityShortcutKeyTargets;
+        } else if (shortcutType == ACCESSIBILITY_BUTTON) {
+            return mAccessibilityButtonTargets;
+        }
+        return null;
     }
 
-    public void setNavBarMagnificationEnabledLocked(boolean enabled) {
-        mIsNavBarMagnificationEnabled = enabled;
+    /**
+     * Whether or not the given shortcut target is installed in device.
+     *
+     * @param name The shortcut target name
+     * @return true if the shortcut target is installed.
+     */
+    public boolean isShortcutTargetInstalledLocked(String name) {
+        if (TextUtils.isEmpty(name)) {
+            return false;
+        }
+        if (MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
+            return true;
+        }
+
+        final ComponentName componentName = ComponentName.unflattenFromString(name);
+        if (componentName == null) {
+            return false;
+        }
+        if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
+                .containsKey(componentName)) {
+            return true;
+        }
+        if (getInstalledServiceInfoLocked(componentName) != null) {
+            return true;
+        }
+        for (int i = 0; i < mInstalledShortcuts.size(); i++) {
+            if (mInstalledShortcuts.get(i).getComponentName().equals(componentName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns installed accessibility service info by the given service component name.
+     */
+    public AccessibilityServiceInfo getInstalledServiceInfoLocked(ComponentName componentName) {
+        for (int i = 0; i < mInstalledServices.size(); i++) {
+            final AccessibilityServiceInfo serviceInfo = mInstalledServices.get(i);
+            if (serviceInfo.getComponentName().equals(componentName)) {
+                return serviceInfo;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns accessibility service connection by the given service component name.
+     */
+    public AccessibilityServiceConnection getServiceConnectionLocked(ComponentName componentName) {
+        return mComponentNameToServiceMap.get(componentName);
     }
 
     public int getNonInteractiveUiTimeoutLocked() {
@@ -557,14 +650,6 @@
         mIsPerformGesturesEnabled = enabled;
     }
 
-    public ComponentName getServiceAssignedToAccessibilityButtonLocked() {
-        return mServiceAssignedToAccessibilityButton;
-    }
-
-    public void setServiceAssignedToAccessibilityButtonLocked(ComponentName componentName) {
-        mServiceAssignedToAccessibilityButton = componentName;
-    }
-
     public ComponentName getServiceChangingSoftKeyboardModeLocked() {
         return mServiceChangingSoftKeyboardMode;
     }
@@ -574,14 +659,6 @@
         mServiceChangingSoftKeyboardMode = serviceChangingSoftKeyboardMode;
     }
 
-    public ComponentName getServiceToEnableWithShortcutLocked() {
-        return mServiceToEnableWithShortcut;
-    }
-
-    public void setServiceToEnableWithShortcutLocked(ComponentName componentName) {
-        mServiceToEnableWithShortcut = componentName;
-    }
-
     public boolean isTextHighContrastEnabledLocked() {
         return mIsTextHighContrastEnabled;
     }
@@ -613,4 +690,28 @@
     public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
         mUserNonInteractiveUiTimeout = timeout;
     }
+
+    // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
+    //  is merged.
+    boolean isNavBarMagnificationEnabledLocked() {
+        return mIsNavBarMagnificationEnabled;
+    }
+
+    void setNavBarMagnificationEnabledLocked(boolean enabled) {
+        mIsNavBarMagnificationEnabled = enabled;
+    }
+
+    boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
+        return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+    }
+
+    ComponentName getServiceAssignedToAccessibilityButtonLocked() {
+        final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
+                : mAccessibilityButtonTargets.valueAt(0);
+        if (targetName == null) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(targetName);
+    }
+    // TODO(a11y shortcut): End
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index d70e164..96d9c47 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -73,7 +73,7 @@
 
     @Mock private AccessibilityUserState.ServiceInfoChangeListener mMockListener;
 
-    @Mock private Context mContext;
+    @Mock private Context mMockContext;
 
     private MockContentResolver mMockResolver;
 
@@ -85,11 +85,11 @@
         FakeSettingsProvider.clearSettingsProvider();
         mMockResolver = new MockContentResolver();
         mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(mContext.getContentResolver()).thenReturn(mMockResolver);
+        when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
         when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME);
         when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo);
 
-        mUserState = new AccessibilityUserState(USER_ID, mContext, mMockListener);
+        mUserState = new AccessibilityUserState(USER_ID, mMockContext, mMockListener);
     }
 
     @After
@@ -109,11 +109,11 @@
         mUserState.setInteractiveUiTimeoutLocked(30);
         mUserState.mEnabledServices.add(COMPONENT_NAME);
         mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
+        mUserState.mAccessibilityShortcutKeyTargets.add(COMPONENT_NAME.flattenToString());
+        mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
         mUserState.setTouchExplorationEnabledLocked(true);
         mUserState.setDisplayMagnificationEnabledLocked(true);
         mUserState.setNavBarMagnificationEnabledLocked(true);
-        mUserState.setServiceAssignedToAccessibilityButtonLocked(COMPONENT_NAME);
-        mUserState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
         mUserState.setAutoclickEnabledLocked(true);
         mUserState.setUserNonInteractiveUiTimeoutLocked(30);
         mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -128,11 +128,11 @@
         assertEquals(0, mUserState.getInteractiveUiTimeoutLocked());
         assertTrue(mUserState.mEnabledServices.isEmpty());
         assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
+        assertTrue(mUserState.mAccessibilityShortcutKeyTargets.isEmpty());
+        assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
         assertFalse(mUserState.isTouchExplorationEnabledLocked());
         assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
         assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
-        assertNull(mUserState.getServiceAssignedToAccessibilityButtonLocked());
-        assertFalse(mUserState.isNavBarMagnificationAssignedToAccessibilityButtonLocked());
         assertFalse(mUserState.isAutoclickEnabledLocked());
         assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
         assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
@@ -287,6 +287,19 @@
         verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
     }
 
+    @Test
+    public void isShortcutTargetInstalledLocked_returnTrue() {
+        mUserState.mInstalledServices.add(mMockServiceInfo);
+        assertTrue(mUserState.isShortcutTargetInstalledLocked(COMPONENT_NAME.flattenToString()));
+    }
+
+    @Test
+    public void isShortcutTargetInstalledLocked_invalidTarget_returnFalse() {
+        final ComponentName invalidTarget =
+                new ComponentName("com.android.server.accessibility", "InvalidTarget");
+        assertFalse(mUserState.isShortcutTargetInstalledLocked(invalidTarget.flattenToString()));
+    }
+
     private int getSecureIntForUser(String key, int userId) {
         return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
     }