Loading core/java/android/view/accessibility/AccessibilityManager.java +37 −0 Original line number Diff line number Diff line Loading @@ -185,15 +185,30 @@ public final class AccessibilityManager { /** * Annotations for the shortcut type. * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p> * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { // LINT.IfChange(shortcut_type_intdef) ACCESSIBILITY_BUTTON, ACCESSIBILITY_SHORTCUT_KEY // LINT.ThenChange(:shortcut_type_array) }) public @interface ShortcutType {} /** * Used for iterating through {@link ShortcutType}. * <p>Note: Keep in sync with {@link ShortcutType}.</p> * @hide */ public static final int[] SHORTCUT_TYPES = { // LINT.IfChange(shortcut_type_array) ACCESSIBILITY_BUTTON, ACCESSIBILITY_SHORTCUT_KEY, // LINT.ThenChange(:shortcut_type_intdef) }; /** * Annotations for content flag of UI. * @hide Loading Loading @@ -913,6 +928,28 @@ public final class AccessibilityManager { } } /** * Returns whether the user must be shown the AccessibilityService warning dialog * before the AccessibilityService (or any shortcut for the service) can be enabled. * @hide */ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) public boolean isAccessibilityServiceWarningRequired(@NonNull AccessibilityServiceInfo info) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); if (service == null) { return true; } } try { return service.isAccessibilityServiceWarningRequired(info); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while checking isAccessibilityServiceWarningRequired: ", re); return true; } } /** * Registers an {@link AccessibilityStateChangeListener} for changes in * the global accessibility state of the system. Equivalent to calling Loading core/java/android/view/accessibility/IAccessibilityManager.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -128,6 +128,9 @@ interface IAccessibilityManager { boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId); boolean sendRestrictedDialogIntent(String packageName, int uid, int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)") boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info); parcelable WindowTransformationSpec { float[] transformationMatrix; MagnificationSpec magnificationSpec; Loading core/java/android/view/accessibility/flags/accessibility_flags.aconfig +6 −6 Original line number Diff line number Diff line Loading @@ -17,17 +17,17 @@ flag { } flag { name: "cleanup_accessibility_warning_dialog" namespace: "accessibility" name: "collection_info_item_counts" description: "Fields for total items and the number of important for accessibility items in a collection" bug: "302376158" description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog." bug: "303511250" } flag { name: "deduplicate_accessibility_warning_dialog" namespace: "accessibility" description: "Removes duplicate definition of the accessibility warning dialog." bug: "303511250" name: "collection_info_item_counts" description: "Fields for total items and the number of important for accessibility items in a collection" bug: "302376158" } flag { Loading core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +30 −8 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.Flags; import android.widget.AdapterView; Loading Loading @@ -114,6 +115,26 @@ public class AccessibilityShortcutChooserActivity extends Activity { private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) { final AccessibilityTarget target = mTargets.get(position); if (Flags.cleanupAccessibilityWarningDialog()) { if (target instanceof AccessibilityServiceTarget serviceTarget) { if (sendRestrictedDialogIntentIfNeeded(target)) { return; } final AccessibilityManager am = getSystemService(AccessibilityManager.class); if (am.isAccessibilityServiceWarningRequired( serviceTarget.getAccessibilityServiceInfo())) { showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, position, mTargetAdapter); return; } } if (target instanceof AccessibilityActivityTarget activityTarget) { if (!activityTarget.isShortcutEnabled() && sendRestrictedDialogIntentIfNeeded(activityTarget)) { return; } } } else { if (!target.isShortcutEnabled()) { if (target instanceof AccessibilityServiceTarget || target instanceof AccessibilityActivityTarget) { Loading @@ -128,6 +149,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { return; } } } target.onCheckedChanged(!target.isShortcutEnabled()); mTargetAdapter.notifyDataSetChanged(); Loading Loading @@ -156,7 +178,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { return; } if (Flags.deduplicateAccessibilityWarningDialog()) { if (Flags.cleanupAccessibilityWarningDialog()) { mPermissionDialog = AccessibilityServiceWarning .createAccessibilityServiceWarningDialog(context, serviceTarget.getAccessibilityServiceInfo(), Loading core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +63 −6 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.internal.accessibility; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.doubleClick; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; Loading Loading @@ -85,10 +84,13 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; /** * Tests for {@link AccessibilityShortcutChooserActivity}. Loading Loading @@ -151,6 +153,8 @@ public class AccessibilityShortcutChooserActivityTest { when(mAccessibilityManagerService.getInstalledAccessibilityServiceList( anyInt())).thenReturn(new ParceledListSlice<>( Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(true); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); Loading @@ -169,7 +173,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsDisabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() { launchActivity(); openShortcutsList(); Loading @@ -183,7 +187,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_allow_rowChecked() { launchActivity(); openShortcutsList(); Loading @@ -197,7 +201,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_deny_rowNotChecked() { launchActivity(); openShortcutsList(); Loading @@ -211,7 +215,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() { launchActivity(); openShortcutsList(); Loading @@ -226,6 +230,59 @@ public class AccessibilityShortcutChooserActivityTest { UI_TIMEOUT_MS)).isFalse(); } @Test @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception { when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(false); launchActivity(); openShortcutsList(); // Clicking the test service should not show a permission dialog window, assertThat(mDevice.findObject(By.text(TEST_LABEL)).clickAndWait( Until.newWindow(), UI_TIMEOUT_MS)).isFalse(); // and should become checked. assertThat(mDevice.findObject(By.checked(true))).isNotNull(); } @Test @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired() throws Exception { when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(false); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false); // This test class mocks AccessibilityManagerService, so the restricted dialog window // will not actually appear and therefore cannot be used for a wait Until.newWindow(). // To still allow smart waiting in this test we can instead set up the mocked method // to update an atomic boolean and wait for that to be set. final Object waitObject = new Object(); final AtomicBoolean calledSendRestrictedDialogIntent = new AtomicBoolean(false); Mockito.doAnswer((Answer<Void>) invocation -> { synchronized (waitObject) { calledSendRestrictedDialogIntent.set(true); waitObject.notify(); } return null; }).when(mAccessibilityManagerService).sendRestrictedDialogIntent( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt()); launchActivity(); openShortcutsList(); mDevice.findObject(By.text(TEST_LABEL)).click(); final long timeout = System.currentTimeMillis() + UI_TIMEOUT_MS; synchronized (waitObject) { while (!calledSendRestrictedDialogIntent.get() && (System.currentTimeMillis() < timeout)) { waitObject.wait(timeout - System.currentTimeMillis()); } } assertThat(calledSendRestrictedDialogIntent.get()).isTrue(); assertThat(mDevice.findObject(By.checked(true))).isNull(); } @Test public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent() throws Exception { Loading Loading @@ -329,7 +386,7 @@ public class AccessibilityShortcutChooserActivityTest { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Flags.deduplicateAccessibilityWarningDialog()) { if (Flags.cleanupAccessibilityWarningDialog()) { // 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); Loading Loading
core/java/android/view/accessibility/AccessibilityManager.java +37 −0 Original line number Diff line number Diff line Loading @@ -185,15 +185,30 @@ public final class AccessibilityManager { /** * Annotations for the shortcut type. * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p> * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { // LINT.IfChange(shortcut_type_intdef) ACCESSIBILITY_BUTTON, ACCESSIBILITY_SHORTCUT_KEY // LINT.ThenChange(:shortcut_type_array) }) public @interface ShortcutType {} /** * Used for iterating through {@link ShortcutType}. * <p>Note: Keep in sync with {@link ShortcutType}.</p> * @hide */ public static final int[] SHORTCUT_TYPES = { // LINT.IfChange(shortcut_type_array) ACCESSIBILITY_BUTTON, ACCESSIBILITY_SHORTCUT_KEY, // LINT.ThenChange(:shortcut_type_intdef) }; /** * Annotations for content flag of UI. * @hide Loading Loading @@ -913,6 +928,28 @@ public final class AccessibilityManager { } } /** * Returns whether the user must be shown the AccessibilityService warning dialog * before the AccessibilityService (or any shortcut for the service) can be enabled. * @hide */ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) public boolean isAccessibilityServiceWarningRequired(@NonNull AccessibilityServiceInfo info) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); if (service == null) { return true; } } try { return service.isAccessibilityServiceWarningRequired(info); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while checking isAccessibilityServiceWarningRequired: ", re); return true; } } /** * Registers an {@link AccessibilityStateChangeListener} for changes in * the global accessibility state of the system. Equivalent to calling Loading
core/java/android/view/accessibility/IAccessibilityManager.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -128,6 +128,9 @@ interface IAccessibilityManager { boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId); boolean sendRestrictedDialogIntent(String packageName, int uid, int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)") boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info); parcelable WindowTransformationSpec { float[] transformationMatrix; MagnificationSpec magnificationSpec; Loading
core/java/android/view/accessibility/flags/accessibility_flags.aconfig +6 −6 Original line number Diff line number Diff line Loading @@ -17,17 +17,17 @@ flag { } flag { name: "cleanup_accessibility_warning_dialog" namespace: "accessibility" name: "collection_info_item_counts" description: "Fields for total items and the number of important for accessibility items in a collection" bug: "302376158" description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog." bug: "303511250" } flag { name: "deduplicate_accessibility_warning_dialog" namespace: "accessibility" description: "Removes duplicate definition of the accessibility warning dialog." bug: "303511250" name: "collection_info_item_counts" description: "Fields for total items and the number of important for accessibility items in a collection" bug: "302376158" } flag { Loading
core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +30 −8 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.Flags; import android.widget.AdapterView; Loading Loading @@ -114,6 +115,26 @@ public class AccessibilityShortcutChooserActivity extends Activity { private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) { final AccessibilityTarget target = mTargets.get(position); if (Flags.cleanupAccessibilityWarningDialog()) { if (target instanceof AccessibilityServiceTarget serviceTarget) { if (sendRestrictedDialogIntentIfNeeded(target)) { return; } final AccessibilityManager am = getSystemService(AccessibilityManager.class); if (am.isAccessibilityServiceWarningRequired( serviceTarget.getAccessibilityServiceInfo())) { showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, position, mTargetAdapter); return; } } if (target instanceof AccessibilityActivityTarget activityTarget) { if (!activityTarget.isShortcutEnabled() && sendRestrictedDialogIntentIfNeeded(activityTarget)) { return; } } } else { if (!target.isShortcutEnabled()) { if (target instanceof AccessibilityServiceTarget || target instanceof AccessibilityActivityTarget) { Loading @@ -128,6 +149,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { return; } } } target.onCheckedChanged(!target.isShortcutEnabled()); mTargetAdapter.notifyDataSetChanged(); Loading Loading @@ -156,7 +178,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { return; } if (Flags.deduplicateAccessibilityWarningDialog()) { if (Flags.cleanupAccessibilityWarningDialog()) { mPermissionDialog = AccessibilityServiceWarning .createAccessibilityServiceWarningDialog(context, serviceTarget.getAccessibilityServiceInfo(), Loading
core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +63 −6 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.internal.accessibility; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.doubleClick; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; Loading Loading @@ -85,10 +84,13 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; /** * Tests for {@link AccessibilityShortcutChooserActivity}. Loading Loading @@ -151,6 +153,8 @@ public class AccessibilityShortcutChooserActivityTest { when(mAccessibilityManagerService.getInstalledAccessibilityServiceList( anyInt())).thenReturn(new ParceledListSlice<>( Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(true); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); Loading @@ -169,7 +173,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsDisabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() { launchActivity(); openShortcutsList(); Loading @@ -183,7 +187,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_allow_rowChecked() { launchActivity(); openShortcutsList(); Loading @@ -197,7 +201,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_deny_rowNotChecked() { launchActivity(); openShortcutsList(); Loading @@ -211,7 +215,7 @@ public class AccessibilityShortcutChooserActivityTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() { launchActivity(); openShortcutsList(); Loading @@ -226,6 +230,59 @@ public class AccessibilityShortcutChooserActivityTest { UI_TIMEOUT_MS)).isFalse(); } @Test @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception { when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(false); launchActivity(); openShortcutsList(); // Clicking the test service should not show a permission dialog window, assertThat(mDevice.findObject(By.text(TEST_LABEL)).clickAndWait( Until.newWindow(), UI_TIMEOUT_MS)).isFalse(); // and should become checked. assertThat(mDevice.findObject(By.checked(true))).isNotNull(); } @Test @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired() throws Exception { when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any())) .thenReturn(false); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false); // This test class mocks AccessibilityManagerService, so the restricted dialog window // will not actually appear and therefore cannot be used for a wait Until.newWindow(). // To still allow smart waiting in this test we can instead set up the mocked method // to update an atomic boolean and wait for that to be set. final Object waitObject = new Object(); final AtomicBoolean calledSendRestrictedDialogIntent = new AtomicBoolean(false); Mockito.doAnswer((Answer<Void>) invocation -> { synchronized (waitObject) { calledSendRestrictedDialogIntent.set(true); waitObject.notify(); } return null; }).when(mAccessibilityManagerService).sendRestrictedDialogIntent( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt()); launchActivity(); openShortcutsList(); mDevice.findObject(By.text(TEST_LABEL)).click(); final long timeout = System.currentTimeMillis() + UI_TIMEOUT_MS; synchronized (waitObject) { while (!calledSendRestrictedDialogIntent.get() && (System.currentTimeMillis() < timeout)) { waitObject.wait(timeout - System.currentTimeMillis()); } } assertThat(calledSendRestrictedDialogIntent.get()).isTrue(); assertThat(mDevice.findObject(By.checked(true))).isNull(); } @Test public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent() throws Exception { Loading Loading @@ -329,7 +386,7 @@ public class AccessibilityShortcutChooserActivityTest { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Flags.deduplicateAccessibilityWarningDialog()) { if (Flags.cleanupAccessibilityWarningDialog()) { // 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); Loading