Tech debt cleanup: Consolidates A11yService warning dialog.

Both frameworks/base and the Settings app define almost-identical copies
of a warning dialog shown when enabling an accessibility service.
The frameworks/base version was used for contexts outside of Settings
(e.g. while editing the volume key shortcut after triggering it with
2+ features already enabled) while the Settings version was used in
the Settings app.

This change replaces the frameworks/base implementation with the more
full-featured Settings implementation.
The other change in this topic in packages/apps/Settings changes
Settings to use this consolidated single version.

Feature flag:
`adb shell device_config override accessibility android.view.accessibility.deduplicate_accessibility_warning_dialog true`

Bug: 303511250
Test: Use this dialog in Accessibility Settings
Test: Use this dialog in the a11y volume-key shortcut editor
Test: atest AccessibilityServiceWarningTest
Test: atest AccessibilityShortcutChooserActivityTest
Change-Id: I6e1acf7cecfaa0dce422f7dfac3f270b9902dfe9
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ab9566e..5296b99 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
 }
 
 flag {
+    name: "deduplicate_accessibility_warning_dialog"
+    namespace: "accessibility"
+    description: "Removes duplicate definition of the accessibility warning dialog."
+    bug: "303511250"
+}
+
+flag {
     namespace: "accessibility"
     name: "force_invert_color"
     description: "Enable force force-dark for smart inversion and dark theme everywhere"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 6497409..2b6913c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -34,6 +34,8 @@
  */
 class AccessibilityServiceTarget extends AccessibilityTarget {
 
+    private final AccessibilityServiceInfo mAccessibilityServiceInfo;
+
     AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
@@ -47,6 +49,7 @@
                 serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
                 serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
                 convertToKey(convertToUserType(shortcutType)));
+        mAccessibilityServiceInfo = serviceInfo;
     }
 
     @Override
@@ -64,4 +67,8 @@
         holder.mLabelView.setEnabled(enabled);
         holder.mStatusView.setEnabled(enabled);
     }
+
+    public AccessibilityServiceInfo getAccessibilityServiceInfo() {
+        return mAccessibilityServiceInfo;
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
new file mode 100644
index 0000000..0f8ced2
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 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.internal.accessibility.dialog;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.BidiFormatter;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+
+/**
+ * Utility class for creating the dialog that asks the user for explicit permission
+ * before an accessibility service is enabled.
+ */
+public class AccessibilityServiceWarning {
+
+    /**
+     * Returns an {@link AlertDialog} to be shown to confirm that the user
+     * wants to enable an {@link android.accessibilityservice.AccessibilityService}.
+     */
+    public static AlertDialog createAccessibilityServiceWarningDialog(@NonNull Context context,
+            @NonNull AccessibilityServiceInfo info,
+            @NonNull View.OnClickListener allowListener,
+            @NonNull View.OnClickListener denyListener,
+            @NonNull View.OnClickListener uninstallListener) {
+        final AlertDialog ad = new AlertDialog.Builder(context)
+                .setView(createAccessibilityServiceWarningDialogContentView(
+                                context, info, allowListener, denyListener, uninstallListener))
+                .setCancelable(true)
+                .create();
+        Window window = ad.getWindow();
+        WindowManager.LayoutParams params = window.getAttributes();
+        params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+        window.setAttributes(params);
+        return ad;
+    }
+
+    @VisibleForTesting
+    public static View createAccessibilityServiceWarningDialogContentView(Context context,
+            AccessibilityServiceInfo info,
+            View.OnClickListener allowListener,
+            View.OnClickListener denyListener,
+            View.OnClickListener uninstallListener) {
+        final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
+        final View content = inflater.inflate(R.layout.accessibility_service_warning, null);
+
+        final Drawable icon;
+        if (info.getResolveInfo().getIconResource() == 0) {
+            icon = context.getDrawable(R.drawable.ic_accessibility_generic);
+        } else {
+            icon = info.getResolveInfo().loadIcon(context.getPackageManager());
+        }
+        final ImageView permissionDialogIcon = content.findViewById(
+                R.id.accessibility_permissionDialog_icon);
+        permissionDialogIcon.setImageDrawable(icon);
+
+        final TextView permissionDialogTitle = content.findViewById(
+                R.id.accessibility_permissionDialog_title);
+        permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
+                getServiceName(context, info)));
+
+        final Button permissionAllowButton = content.findViewById(
+                R.id.accessibility_permission_enable_allow_button);
+        final Button permissionDenyButton = content.findViewById(
+                R.id.accessibility_permission_enable_deny_button);
+        permissionAllowButton.setOnClickListener(allowListener);
+        permissionAllowButton.setOnTouchListener(getTouchConsumingListener());
+        permissionDenyButton.setOnClickListener(denyListener);
+
+        final Button uninstallButton = content.findViewById(
+                R.id.accessibility_permission_enable_uninstall_button);
+        // Show an uninstall button to help users quickly remove non-preinstalled apps.
+        if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
+            uninstallButton.setVisibility(View.VISIBLE);
+            uninstallButton.setOnClickListener(uninstallListener);
+        }
+        return content;
+    }
+
+    @VisibleForTesting
+    @SuppressLint("ClickableViewAccessibility") // Touches are intentionally consumed
+    public static View.OnTouchListener getTouchConsumingListener() {
+        return (view, event) -> {
+            // Filter obscured touches by consuming them.
+            if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
+                    || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    Toast.makeText(view.getContext(),
+                            R.string.accessibility_dialog_touch_filtered_warning,
+                            Toast.LENGTH_SHORT).show();
+                }
+                return true;
+            }
+            return false;
+        };
+    }
+
+    // Get the service name and bidi wrap it to protect from bidi side effects.
+    private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
+        final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
+        final CharSequence label =
+                info.getResolveInfo().loadLabel(context.getPackageManager());
+        return BidiFormatter.getInstance(locale).unicodeWrap(label);
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 987c14c..d4eccd4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -56,7 +57,7 @@
             "accessibility_shortcut_menu_mode";
     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
     private AlertDialog mMenuDialog;
-    private AlertDialog mPermissionDialog;
+    private Dialog mPermissionDialog;
     private ShortcutTargetAdapter mTargetAdapter;
 
     @Override
@@ -123,7 +124,7 @@
 
             if (target instanceof AccessibilityServiceTarget) {
                 showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
-                        mTargetAdapter);
+                        position, mTargetAdapter);
                 return;
             }
         }
@@ -149,20 +150,43 @@
     }
 
     private void showPermissionDialogIfNeeded(Context context,
-            AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) {
+            AccessibilityServiceTarget serviceTarget, int position,
+            ShortcutTargetAdapter targetAdapter) {
         if (mPermissionDialog != null) {
             return;
         }
 
-        mPermissionDialog = new AlertDialog.Builder(context)
-                .setView(createEnableDialogContentView(context, serviceTarget,
-                        v -> {
-                            mPermissionDialog.dismiss();
-                            targetAdapter.notifyDataSetChanged();
-                        },
-                        v -> mPermissionDialog.dismiss()))
-                .setOnDismissListener(dialog -> mPermissionDialog = null)
-                .create();
+        if (Flags.deduplicateAccessibilityWarningDialog()) {
+            mPermissionDialog = AccessibilityServiceWarning
+                    .createAccessibilityServiceWarningDialog(context,
+                            serviceTarget.getAccessibilityServiceInfo(),
+                            v -> {
+                                serviceTarget.onCheckedChanged(true);
+                                targetAdapter.notifyDataSetChanged();
+                                mPermissionDialog.dismiss();
+                            }, v -> {
+                                serviceTarget.onCheckedChanged(false);
+                                mPermissionDialog.dismiss();
+                            },
+                            v -> {
+                                mTargets.remove(position);
+                                context.getPackageManager().getPackageInstaller().uninstall(
+                                        serviceTarget.getComponentName().getPackageName(), null);
+                                targetAdapter.notifyDataSetChanged();
+                                mPermissionDialog.dismiss();
+                            });
+            mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null);
+        } else {
+            mPermissionDialog = new AlertDialog.Builder(context)
+                    .setView(createEnableDialogContentView(context, serviceTarget,
+                            v -> {
+                                mPermissionDialog.dismiss();
+                                targetAdapter.notifyDataSetChanged();
+                            },
+                            v -> mPermissionDialog.dismiss()))
+                    .setOnDismissListener(dialog -> mPermissionDialog = null)
+                    .create();
+        }
         mPermissionDialog.show();
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 0f85075..51a5ddf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -296,6 +296,10 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link AccessibilityServiceWarning}.
+     */
+    @Deprecated
     static View createEnableDialogContentView(Context context,
             AccessibilityServiceTarget target, View.OnClickListener allowListener,
             View.OnClickListener denyListener) {
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 20d8d91..62d58b6 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -165,6 +165,7 @@
 
     <!-- AccessibilityShortcutChooserActivityTest permissions -->
     <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
 
     <application
         android:theme="@style/Theme"
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index dd116b5..088b57f 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -44,14 +44,19 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
+import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -62,6 +67,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
@@ -90,12 +96,17 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutChooserActivityTest {
     private static final String ONE_HANDED_MODE = "One-Handed mode";
+    private static final String ALLOW_LABEL = "Allow";
     private static final String DENY_LABEL = "Deny";
+    private static final String UNINSTALL_LABEL = "Uninstall";
     private static final String EDIT_LABEL = "Edit shortcuts";
     private static final String LIST_TITLE_LABEL = "Choose features to use";
     private static final String TEST_LABEL = "TEST_LABEL";
-    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+    private static final String TEST_PACKAGE = "TEST_LABEL";
+    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(TEST_PACKAGE,
+            "class");
     private static final long UI_TIMEOUT_MS = 1000;
+    private UiAutomation mUiAutomation;
     private UiDevice mDevice;
     private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
     private TestAccessibilityShortcutChooserActivity mActivity;
@@ -117,6 +128,10 @@
     private IAccessibilityManager mAccessibilityManagerService;
     @Mock
     private KeyguardManager mKeyguardManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private PackageInstaller mPackageInstaller;
 
     @Before
     public void setUp() throws Exception {
@@ -125,6 +140,7 @@
         assumeFalse("AccessibilityShortcutChooserActivity not supported on watch",
                 pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
 
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mDevice.wakeUp();
         when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
@@ -134,12 +150,15 @@
         when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
                 anyInt())).thenReturn(new ParceledListSlice<>(
-                        Collections.singletonList(mAccessibilityServiceInfo)));
+                Collections.singletonList(mAccessibilityServiceInfo)));
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 anyString(), anyInt(), anyInt())).thenReturn(true);
         when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+        when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller);
+
         TestAccessibilityShortcutChooserActivity.setupForTesting(
-                mAccessibilityManagerService, mKeyguardManager);
+                mAccessibilityManagerService, mKeyguardManager,
+                mPackageManager);
     }
 
     @After
@@ -150,18 +169,12 @@
     }
 
     @Test
-    public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
+    @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+    public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() {
         launchActivity();
         openShortcutsList();
 
-        // Performing the double-click is flaky so retry if needed.
-        for (int attempt = 1; attempt <= 2; attempt++) {
-            onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
-            if (mDevice.wait(Until.hasObject(By.text(DENY_LABEL)), UI_TIMEOUT_MS)) {
-                break;
-            }
-        }
-
+        mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
         onView(withText(DENY_LABEL)).perform(scrollTo(), click());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -170,6 +183,50 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+    public void selectTestService_permissionDialog_allow_rowChecked() {
+        launchActivity();
+        openShortcutsList();
+
+        mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+        clickSystemDialogButton(ALLOW_LABEL);
+
+        assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+                UI_TIMEOUT_MS)).isTrue();
+        assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+    public void selectTestService_permissionDialog_deny_rowNotChecked() {
+        launchActivity();
+        openShortcutsList();
+
+        mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+        clickSystemDialogButton(DENY_LABEL);
+
+        assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+                UI_TIMEOUT_MS)).isTrue();
+        assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isFalse();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+    public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
+        launchActivity();
+        openShortcutsList();
+
+        mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+        clickSystemDialogButton(UNINSTALL_LABEL);
+
+        verify(mPackageInstaller).uninstall(eq(TEST_PACKAGE), any());
+        assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+                UI_TIMEOUT_MS)).isTrue();
+        assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(TEST_LABEL)),
+                UI_TIMEOUT_MS)).isFalse();
+    }
+
+    @Test
     public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
             throws Exception {
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
@@ -239,6 +296,18 @@
         mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS);
     }
 
+    private void clickSystemDialogButton(String dialogButtonText) {
+        // Use UiAutomation to find the button because UiDevice struggles to find
+        // a UI element in a system dialog.
+        final AccessibilityNodeInfo button =
+                mUiAutomation.getRootInActiveWindow()
+                        .findAccessibilityNodeInfosByText(dialogButtonText).stream()
+                        .filter(AccessibilityNodeInfo::isClickable).findFirst().get();
+        final Rect bounds = new Rect();
+        button.getBoundsInScreen(bounds);
+        mDevice.click(bounds.centerX(), bounds.centerY());
+    }
+
     /**
      * Used for testing.
      */
@@ -246,12 +315,30 @@
             AccessibilityShortcutChooserActivity {
         private static IAccessibilityManager sAccessibilityManagerService;
         private static KeyguardManager sKeyguardManager;
+        private static PackageManager sPackageManager;
 
         public static void setupForTesting(
                 IAccessibilityManager accessibilityManagerService,
-                KeyguardManager keyguardManager) {
+                KeyguardManager keyguardManager,
+                PackageManager packageManager) {
             sAccessibilityManagerService = accessibilityManagerService;
             sKeyguardManager = keyguardManager;
+            sPackageManager = packageManager;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (Flags.deduplicateAccessibilityWarningDialog()) {
+                // Setting the Theme is necessary here for the dialog to use the proper style
+                // resources as designated in its layout XML.
+                setTheme(R.style.Theme_DeviceDefault_DayNight);
+            }
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return sPackageManager;
         }
 
         @Override
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
new file mode 100644
index 0000000..b76dd51
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 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.internal.accessibility.dialog;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.TestUtils;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Unit Tests for
+ * {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning}
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@RequiresFlagsEnabled(
+        android.view.accessibility.Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+public class AccessibilityServiceWarningTest {
+    private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService";
+    private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary";
+    private static final String A11Y_SERVICE_COMPONENT_NAME =
+            "fake.package/test.a11yservice.name";
+
+    private Context mContext;
+    private AccessibilityServiceInfo mAccessibilityServiceInfo;
+    private AtomicBoolean mAllowListener;
+    private AtomicBoolean mDenyListener;
+    private AtomicBoolean mUninstallListener;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mAccessibilityServiceInfo = TestUtils.createFakeServiceInfo(
+                A11Y_SERVICE_PACKAGE_LABEL,
+                A11Y_SERVICE_SUMMARY,
+                A11Y_SERVICE_COMPONENT_NAME,
+                /* isAlwaysOnService*/ false);
+        mAllowListener = new AtomicBoolean(false);
+        mDenyListener = new AtomicBoolean(false);
+        mUninstallListener = new AtomicBoolean(false);
+    }
+
+    @Test
+    public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams() {
+        final AlertDialog dialog =
+                AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
+                        mContext,
+                        mAccessibilityServiceInfo,
+                        null, null, null);
+        final Window dialogWindow = dialog.getWindow();
+        assertThat(dialogWindow).isNotNull();
+
+        expect.that(dialogWindow.getAttributes().privateFlags
+                & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
+                SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
+    }
+
+    @Test
+    public void createAccessibilityServiceWarningDialog_hasExpectedServiceName() {
+        final TextView title = createDialogContentView().findViewById(
+                R.id.accessibility_permissionDialog_title);
+        assertThat(title).isNotNull();
+
+        assertThat(title.getText().toString()).contains(A11Y_SERVICE_PACKAGE_LABEL);
+    }
+
+    @Test
+    public void createAccessibilityServiceWarningDialog_clickAllow() {
+        final View allowButton = createDialogContentView().findViewById(
+                R.id.accessibility_permission_enable_allow_button);
+        assertThat(allowButton).isNotNull();
+
+        allowButton.performClick();
+
+        expect.that(mAllowListener.get()).isTrue();
+        expect.that(mDenyListener.get()).isFalse();
+        expect.that(mUninstallListener.get()).isFalse();
+    }
+
+    @Test
+    public void createAccessibilityServiceWarningDialog_clickDeny() {
+        final View denyButton = createDialogContentView().findViewById(
+                R.id.accessibility_permission_enable_deny_button);
+        assertThat(denyButton).isNotNull();
+
+        denyButton.performClick();
+
+        expect.that(mAllowListener.get()).isFalse();
+        expect.that(mDenyListener.get()).isTrue();
+        expect.that(mUninstallListener.get()).isFalse();
+    }
+
+    @Test
+    public void createAccessibilityServiceWarningDialog_clickUninstall() {
+        final View uninstallButton = createDialogContentView().findViewById(
+                R.id.accessibility_permission_enable_uninstall_button);
+        assertThat(uninstallButton).isNotNull();
+
+        uninstallButton.performClick();
+
+        expect.that(mAllowListener.get()).isFalse();
+        expect.that(mDenyListener.get()).isFalse();
+        expect.that(mUninstallListener.get()).isTrue();
+    }
+
+    @Test
+    public void getTouchConsumingListener() {
+        final View allowButton = createDialogContentView().findViewById(
+                R.id.accessibility_permission_enable_allow_button);
+        assertThat(allowButton).isNotNull();
+        final View.OnTouchListener listener =
+                AccessibilityServiceWarning.getTouchConsumingListener();
+
+        expect.that(listener.onTouch(allowButton, createMotionEvent(0))).isFalse();
+        expect.that(listener.onTouch(allowButton,
+                createMotionEvent(MotionEvent.FLAG_WINDOW_IS_OBSCURED))).isTrue();
+        expect.that(listener.onTouch(allowButton,
+                createMotionEvent(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED))).isTrue();
+    }
+
+    private View createDialogContentView() {
+        return AccessibilityServiceWarning.createAccessibilityServiceWarningDialogContentView(
+                mContext,
+                mAccessibilityServiceInfo,
+                (v) -> mAllowListener.set(true),
+                (v) -> mDenyListener.set(true),
+                (v) -> mUninstallListener.set(true));
+    }
+
+    private MotionEvent createMotionEvent(int flags) {
+        MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[]{
+                new MotionEvent.PointerProperties()
+        };
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[]{
+                new MotionEvent.PointerCoords()
+        };
+        return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1, props, coords,
+                0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN, flags);
+    }
+}