diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 9504852f6e98b..46b08cf3f13f6 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2210,6 +2210,58 @@ public final class AccessibilityManager {
}
}
+ /**
+ * Determines if the accessibility target is allowed.
+ *
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param userId The id of the user for whom to perform the operation.
+ * @return {@code true} the accessibility target is allowed.
+ * @hide
+ */
+ public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+
+ try {
+ return service.isAccessibilityTargetAllowed(packageName, uid, userId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while check accessibility target status", re);
+ return false;
+ }
+ }
+
+ /**
+ * Sends restricted dialog intent if the accessibility target is disallowed.
+ *
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param userId The id of the user for whom to perform the operation.
+ * @return {@code true} if the restricted dialog is shown.
+ * @hide
+ */
+ public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+
+ try {
+ return service.sendRestrictedDialogIntent(packageName, uid, userId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while show restricted dialog", re);
+ return false;
+ }
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1302421d0ce2e..d95f4ffc0e9fe 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -126,6 +126,9 @@ interface IAccessibilityManager {
boolean stopFlashNotificationSequence(String opPkg);
boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
+ boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
+ boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);
+
parcelable WindowTransformationSpec {
float[] transformationMatrix;
MagnificationSpec magnificationSpec;
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 4c7d93b7446a8..063154d9a6d6a 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
/**
* Base class for creating accessibility activity target.
@@ -40,8 +41,25 @@ class AccessibilityActivityTarget extends AccessibilityTarget {
isShortcutContained(context, shortcutType,
shortcutInfo.getComponentName().flattenToString()),
shortcutInfo.getComponentName().flattenToString(),
+ shortcutInfo.getActivityInfo().applicationInfo.uid,
shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
convertToKey(convertToUserType(shortcutType)));
}
+
+ @Override
+ public void updateActionItem(@NonNull TargetAdapter.ViewHolder holder,
+ @ShortcutMenuMode int shortcutMenuMode) {
+ super.updateActionItem(holder, shortcutMenuMode);
+
+ final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+ getContext(), getComponentName().getPackageName(), getUid());
+ final boolean isEditMenuMode =
+ shortcutMenuMode == ShortcutMenuMode.EDIT;
+ final boolean enabled = isAllowed || (isEditMenuMode && isShortcutEnabled());
+ holder.mCheckBoxView.setEnabled(enabled);
+ holder.mIconView.setEnabled(enabled);
+ holder.mLabelView.setEnabled(enabled);
+ holder.mStatusView.setEnabled(enabled);
+ }
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index e64f78a3f2070..64974090938f0 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
/**
* Base class for creating accessibility service target with various fragment types related to
@@ -42,8 +43,25 @@ class AccessibilityServiceTarget extends AccessibilityTarget {
isShortcutContained(context, shortcutType,
serviceInfo.getComponentName().flattenToString()),
serviceInfo.getComponentName().flattenToString(),
+ serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
convertToKey(convertToUserType(shortcutType)));
}
+
+ @Override
+ public void updateActionItem(@NonNull TargetAdapter.ViewHolder holder,
+ @ShortcutMenuMode int shortcutMenuMode) {
+ super.updateActionItem(holder, shortcutMenuMode);
+
+ final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+ getContext(), getComponentName().getPackageName(), getUid());
+ final boolean isEditMenuMode =
+ shortcutMenuMode == ShortcutMenuMode.EDIT;
+ final boolean enabled = isAllowed || (isEditMenuMode && isShortcutEnabled());
+ holder.mCheckBoxView.setEnabled(enabled);
+ holder.mIconView.setEnabled(enabled);
+ holder.mLabelView.setEnabled(enabled);
+ holder.mStatusView.setEnabled(enabled);
+ }
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 50afb3ee0a03c..5dd558a5f850e 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -95,6 +95,13 @@ public class AccessibilityShortcutChooserActivity extends Activity {
private void onTargetSelected(AdapterView> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
+ if (target instanceof AccessibilityServiceTarget
+ || target instanceof AccessibilityActivityTarget) {
+ if (sendRestrictedDialogIntentIfNeeded(target)) {
+ return;
+ }
+ }
+
target.onSelected();
mMenuDialog.dismiss();
}
@@ -102,15 +109,41 @@ public class AccessibilityShortcutChooserActivity extends Activity {
private void onTargetChecked(AdapterView> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
- if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
- showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, mTargetAdapter);
- return;
+ if (!target.isShortcutEnabled()) {
+ if (target instanceof AccessibilityServiceTarget
+ || target instanceof AccessibilityActivityTarget) {
+ if (sendRestrictedDialogIntentIfNeeded(target)) {
+ return;
+ }
+ }
+
+ if (target instanceof AccessibilityServiceTarget) {
+ showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
+ mTargetAdapter);
+ return;
+ }
}
target.onCheckedChanged(!target.isShortcutEnabled());
mTargetAdapter.notifyDataSetChanged();
}
+ /**
+ * Sends restricted dialog intent if the accessibility target is disallowed.
+ *
+ * @return true if sends restricted dialog intent, otherwise false.
+ */
+ private boolean sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target) {
+ if (AccessibilityTargetHelper.isAccessibilityTargetAllowed(this,
+ target.getComponentName().getPackageName(), target.getUid())) {
+ return false;
+ }
+
+ AccessibilityTargetHelper.sendRestrictedDialogIntent(this,
+ target.getComponentName().getPackageName(), target.getUid());
+ return true;
+ }
+
private void showPermissionDialogIfNeeded(Context context,
AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) {
if (mPermissionDialog != null) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index b8446da3c5fcc..652cb52334610 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -25,6 +25,7 @@ import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueF
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
@@ -37,8 +38,11 @@ import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
/**
- * Abstract base class for creating various target related to accessibility service,
- * accessibility activity, and allowlisting feature.
+ * Abstract base class for creating various target related to accessibility service, accessibility
+ * activity, and allowlisting features.
+ *
+ *
Disables accessibility features that are not permitted in adding a restricted padlock icon
+ * and showing admin support message dialog.
*/
public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
OnTargetCheckedChangeListener {
@@ -49,6 +53,8 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
private int mFragmentType;
private boolean mShortcutEnabled;
private String mId;
+ private int mUid;
+ private ComponentName mComponentName;
private CharSequence mLabel;
private Drawable mIcon;
private String mKey;
@@ -57,12 +63,14 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
@VisibleForTesting
public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
- CharSequence label, Drawable icon, String key) {
+ int uid, CharSequence label, Drawable icon, String key) {
mContext = context;
mShortcutType = shortcutType;
mFragmentType = fragmentType;
mShortcutEnabled = isShortcutSwitched;
mId = id;
+ mUid = uid;
+ mComponentName = ComponentName.unflattenFromString(id);
mLabel = label;
mIcon = icon;
mKey = key;
@@ -71,9 +79,14 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
@Override
public void updateActionItem(@NonNull ViewHolder holder,
@ShortcutConstants.ShortcutMenuMode int shortcutMenuMode) {
+ // Resetting the enable state of the item to avoid the previous wrong state of RecyclerView.
+ holder.mCheckBoxView.setEnabled(true);
+ holder.mIconView.setEnabled(true);
+ holder.mLabelView.setEnabled(true);
+ holder.mStatusView.setEnabled(true);
+
final boolean isEditMenuMode =
shortcutMenuMode == ShortcutConstants.ShortcutMenuMode.EDIT;
-
holder.mCheckBoxView.setChecked(isEditMenuMode && isShortcutEnabled());
holder.mCheckBoxView.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
holder.mIconView.setImageDrawable(getIcon());
@@ -145,6 +158,14 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
return mId;
}
+ public int getUid() {
+ return mUid;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
public CharSequence getLabel() {
return mLabel;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index a47a97c02bfbc..0f85075a0d6c9 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -35,6 +35,7 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.BidiFormatter;
import android.view.LayoutInflater;
@@ -202,12 +203,14 @@ public final class AccessibilityTargetHelper {
private static List getAllowListingFeatureTargets(Context context,
@ShortcutType int shortcutType) {
final List targets = new ArrayList<>();
+ final int uid = context.getApplicationInfo().uid;
final InvisibleToggleAllowListingFeatureTarget magnification =
new InvisibleToggleAllowListingFeatureTarget(context,
shortcutType,
isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
MAGNIFICATION_CONTROLLER_NAME,
+ uid,
context.getString(R.string.accessibility_magnification_chooser_text),
context.getDrawable(R.drawable.ic_accessibility_magnification),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
@@ -219,6 +222,7 @@ public final class AccessibilityTargetHelper {
isShortcutContained(context, shortcutType,
DALTONIZER_COMPONENT_NAME.flattenToString()),
DALTONIZER_COMPONENT_NAME.flattenToString(),
+ uid,
context.getString(R.string.color_correction_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_correction),
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
@@ -230,6 +234,7 @@ public final class AccessibilityTargetHelper {
isShortcutContained(context, shortcutType,
COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+ uid,
context.getString(R.string.color_inversion_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_inversion),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -242,6 +247,7 @@ public final class AccessibilityTargetHelper {
isShortcutContained(context, shortcutType,
ONE_HANDED_COMPONENT_NAME.flattenToString()),
ONE_HANDED_COMPONENT_NAME.flattenToString(),
+ uid,
context.getString(R.string.one_handed_mode_feature_name),
context.getDrawable(R.drawable.ic_accessibility_one_handed),
Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
@@ -254,6 +260,7 @@ public final class AccessibilityTargetHelper {
isShortcutContained(context, shortcutType,
REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
+ uid,
context.getString(R.string.reduce_bright_colors_feature_name),
context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
@@ -265,6 +272,7 @@ public final class AccessibilityTargetHelper {
isShortcutContained(context, shortcutType,
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()),
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString(),
+ uid,
context.getString(R.string.hearing_aids_feature_name),
context.getDrawable(R.drawable.ic_accessibility_hearing_aid),
/* key= */ null);
@@ -327,4 +335,21 @@ public final class AccessibilityTargetHelper {
final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
return BidiFormatter.getInstance(locale).unicodeWrap(label);
}
+
+ /**
+ * Determines if the{@link AccessibilityTarget} is allowed.
+ */
+ public static boolean isAccessibilityTargetAllowed(Context context, String packageName,
+ int uid) {
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ return am.isAccessibilityTargetAllowed(packageName, uid, UserHandle.myUserId());
+ }
+
+ /**
+ * Sends restricted dialog intent if the accessibility target is disallowed.
+ */
+ public static boolean sendRestrictedDialogIntent(Context context, String packageName, int uid) {
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ return am.sendRestrictedDialogIntent(packageName, uid, UserHandle.myUserId());
+ }
}
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index e78036d9f1e9b..c22f17dfa9671 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -29,8 +29,9 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility
class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
- boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
- super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
- isShortcutSwitched, id, label, icon, key);
+ boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
+ String key) {
+ super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
+ id, uid, label, icon, key);
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index 41a0ba2b693aa..a4ffef6bfbc2f 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -29,12 +29,22 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Extension for {@link AccessibilityServiceTarget} with {@link AccessibilityFragmentType#TOGGLE}
* type.
*/
class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
+ /** Float enum for view alpha setting. */
+ @Retention(RetentionPolicy.SOURCE)
+ @interface StatusViewAlphaScale {
+ float OPAQUE = 1.0f;
+ float DISABLED = 0.5f;
+ }
+
ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
@@ -53,9 +63,13 @@ class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
@ShortcutMenuMode int shortcutMenuMode) {
super.updateActionItem(holder, shortcutMenuMode);
+ final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+ getContext(), getComponentName().getPackageName(), getUid());
final boolean isEditMenuMode =
shortcutMenuMode == ShortcutMenuMode.EDIT;
holder.mStatusView.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
holder.mStatusView.setText(getStateDescription());
+ holder.mStatusView.setAlpha(isAllowed
+ ? StatusViewAlphaScale.OPAQUE : StatusViewAlphaScale.DISABLED);
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index d2124a00596f3..11e668f517744 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -35,9 +35,10 @@ import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
- boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
- super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
- isShortcutSwitched, id, label, icon, key);
+ boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
+ String key) {
+ super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
+ uid, label, icon, key);
final int statusResId = isFeatureEnabled()
? R.string.accessibility_shortcut_menu_item_status_on
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 973b904c9344f..03d366e6e5522 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -33,6 +33,9 @@ import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -54,6 +57,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +75,7 @@ public class AccessibilityShortcutChooserActivityTest {
private static final String ONE_HANDED_MODE = "One-Handed mode";
private static final String TEST_LABEL = "TEST_LABEL";
private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+ private TestAccessibilityShortcutChooserActivity mActivity;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -85,10 +90,22 @@ public class AccessibilityShortcutChooserActivityTest {
@Mock
private IAccessibilityManager mAccessibilityManagerService;
+ @Before
+ public void setUp() throws Exception {
+ when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+ mResolveInfo.serviceInfo = mServiceInfo;
+ mServiceInfo.applicationInfo = mApplicationInfo;
+ when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+ when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+ when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
+ anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+ when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
+ anyString(), anyInt(), anyInt())).thenReturn(true);
+ TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
+ }
+
@Test
- public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()
- throws Exception {
- configureTestService();
+ public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
final ActivityScenario scenario =
ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
scenario.moveToState(Lifecycle.State.CREATED);
@@ -101,12 +118,36 @@ public class AccessibilityShortcutChooserActivityTest {
onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
click());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
doesNotExist());
scenario.moveToState(Lifecycle.State.DESTROYED);
}
+ @Test
+ public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
+ throws Exception {
+ when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
+ eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
+ final ActivityScenario scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.onActivity(activity -> mActivity = activity);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ onView(withText(TEST_LABEL)).perform(scrollTo(), click());
+ verify(mAccessibilityManagerService).sendRestrictedDialogIntent(
+ eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
@Test
public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
@@ -145,18 +186,6 @@ public class AccessibilityShortcutChooserActivityTest {
scenario.moveToState(Lifecycle.State.DESTROYED);
}
- private void configureTestService() throws Exception {
- when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
- mResolveInfo.serviceInfo = mServiceInfo;
- mServiceInfo.applicationInfo = mApplicationInfo;
- when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
- when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
- when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
- anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
-
- TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
- }
-
/**
* Used for testing.
*/
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7fba72b74e8d6..26400940bf7d7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -42,6 +42,7 @@ import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.Manifest;
import android.accessibilityservice.AccessibilityGestureEvent;
@@ -57,8 +58,10 @@ import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.AlertDialog;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -159,6 +162,7 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.android.settingslib.RestrictedLockUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -3898,6 +3902,51 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ @Override
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_ADMIN_POLICY})
+ public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
+ final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ final List permittedServices = dpm.getPermittedAccessibilityServices(userId);
+
+ // permittedServices null means all accessibility services are allowed.
+ boolean allowed = permittedServices == null || permittedServices.contains(packageName);
+ if (allowed) {
+ final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ final int mode = appOps.noteOpNoThrow(
+ AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName, /* attributionTag= */ null, /* message= */ null);
+ final boolean ecmEnabled = mContext.getResources().getBoolean(
+ R.bool.config_enhancedConfirmationModeEnabled);
+ return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+ }
+ return false;
+ }
+
+ @Override
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_ADMIN_POLICY})
+ public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
+ // The accessibility service is allowed. Don't show the restricted dialog.
+ if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
+ return false;
+ }
+
+ final EnforcedAdmin admin =
+ RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
+ mContext, packageName, userId);
+ if (admin != null) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, admin);
+ return true;
+ }
+
+ RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
+ packageName, uid);
+ return true;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 48060017e6d2c..c37ea501bbc9c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -24,9 +24,7 @@ import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +44,6 @@ import android.view.inputmethod.InputMethodInfo;
import com.android.internal.util.ArrayUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.settingslib.RestrictedLockUtils;
import libcore.util.EmptyArray;
@@ -420,7 +417,7 @@ public class AccessibilitySecurityPolicy {
// TODO(b/207697949, b/208872785): Add cts test for managed device.
// Use RestrictedLockUtilsInternal in AccessibilitySecurityPolicy
- if (checkIfInputMethodDisallowed(
+ if (RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
mContext, inputMethodInfo.getPackageName(), callingUserId) != null) {
return ENABLE_IME_FAIL_BY_ADMIN;
}
@@ -428,72 +425,6 @@ public class AccessibilitySecurityPolicy {
return ENABLE_IME_SUCCESS;
}
- /**
- * @return the UserHandle for a userId. Return null for USER_NULL
- */
- private static UserHandle getUserHandleOf(@UserIdInt int userId) {
- if (userId == UserHandle.USER_NULL) {
- return null;
- } else {
- return UserHandle.of(userId);
- }
- }
-
- private static int getManagedProfileId(Context context, int userId) {
- UserManager um = context.getSystemService(UserManager.class);
- List userProfiles = um.getProfiles(userId);
- for (UserInfo uInfo : userProfiles) {
- if (uInfo.id == userId) {
- continue;
- }
- if (uInfo.isManagedProfile()) {
- return uInfo.id;
- }
- }
- return UserHandle.USER_NULL;
- }
-
- private static RestrictedLockUtils.EnforcedAdmin checkIfInputMethodDisallowed(Context context,
- String packageName, int userId) {
- DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
- return null;
- }
- RestrictedLockUtils.EnforcedAdmin admin =
- RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
- boolean permitted = true;
- if (admin != null) {
- permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
- packageName, userId);
- }
-
- boolean permittedByParentAdmin = true;
- RestrictedLockUtils.EnforcedAdmin profileAdmin = null;
- int managedProfileId = getManagedProfileId(context, userId);
- if (managedProfileId != UserHandle.USER_NULL) {
- profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
- context, getUserHandleOf(managedProfileId));
- // If the device is an organization-owned device with a managed profile, the
- // managedProfileId will be used instead of the affected userId. This is because
- // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
- // return results affecting the personal profile.
- if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
- DevicePolicyManager parentDpm = dpm.getParentProfileInstance(
- UserManager.get(context).getUserInfo(managedProfileId));
- permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
- profileAdmin.component, packageName, managedProfileId);
- }
- }
- if (!permitted && !permittedByParentAdmin) {
- return RestrictedLockUtils.EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
- } else if (!permitted) {
- return admin;
- } else if (!permittedByParentAdmin) {
- return profileAdmin;
- }
- return null;
- }
-
/**
* Returns the parent userId of the profile according to the specified userId.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java b/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java
new file mode 100644
index 0000000000000..28810e6fa5520
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java
@@ -0,0 +1,140 @@
+/*
+ * 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.server.accessibility;
+
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settingslib.RestrictedLockUtils;
+
+import java.util.List;
+
+/**
+ * Utility class to host methods usable to return {@link EnforcedAdmin} instances based on device
+ * admin policy.
+ */
+public class RestrictedLockUtilsInternal {
+
+ /**
+ * Disables accessibility service that are not permitted.
+ */
+ public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context,
+ String packageName, int userId) {
+ final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return null;
+ }
+ final EnforcedAdmin admin =
+ RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+ boolean permitted = true;
+ if (admin != null) {
+ permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
+ packageName, userId);
+ }
+ int managedProfileId = getManagedProfileId(context, userId);
+ final EnforcedAdmin profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(context,
+ getUserHandleOf(managedProfileId));
+ boolean permittedByProfileAdmin = true;
+ if (profileAdmin != null) {
+ permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
+ profileAdmin.component, packageName, managedProfileId);
+ }
+ if (!permitted && !permittedByProfileAdmin) {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ } else if (!permitted) {
+ return admin;
+ } else if (!permittedByProfileAdmin) {
+ return profileAdmin;
+ }
+ return null;
+ }
+
+ /**
+ * Disables input method that are not permitted.
+ */
+ public static EnforcedAdmin checkIfInputMethodDisallowed(Context context, String packageName,
+ int userId) {
+ final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return null;
+ }
+ final EnforcedAdmin admin =
+ RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+ boolean permitted = true;
+ if (admin != null) {
+ permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
+ packageName, userId);
+ }
+
+ boolean permittedByParentAdmin = true;
+ EnforcedAdmin profileAdmin = null;
+ int managedProfileId = getManagedProfileId(context, userId);
+ if (managedProfileId != UserHandle.USER_NULL) {
+ profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
+ context, getUserHandleOf(managedProfileId));
+ // If the device is an organization-owned device with a managed profile, the
+ // managedProfileId will be used instead of the affected userId. This is because
+ // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
+ // return results affecting the personal profile.
+ if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
+ final DevicePolicyManager parentDpm = dpm.getParentProfileInstance(
+ UserManager.get(context).getUserInfo(managedProfileId));
+ permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
+ profileAdmin.component, packageName, managedProfileId);
+ }
+ }
+ if (!permitted && !permittedByParentAdmin) {
+ return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+ } else if (!permitted) {
+ return admin;
+ } else if (!permittedByParentAdmin) {
+ return profileAdmin;
+ }
+ return null;
+ }
+
+ private static int getManagedProfileId(Context context, int userId) {
+ final UserManager um = context.getSystemService(UserManager.class);
+ final List userProfiles = um.getProfiles(userId);
+ for (UserInfo uInfo : userProfiles) {
+ if (uInfo.id == userId) {
+ continue;
+ }
+ if (uInfo.isManagedProfile()) {
+ return uInfo.id;
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
+ /**
+ * @return the UserHandle for a userId. Return null for USER_NULL
+ */
+ private static UserHandle getUserHandleOf(@UserIdInt int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ return null;
+ } else {
+ return UserHandle.of(userId);
+ }
+ }
+}