Merge "Fix A11y features blocked by IT admin can be bypassed using a11y shortcuts" into udc-dev

This commit is contained in:
TreeHugger Robot
2023-03-11 20:08:46 +00:00
committed by Android (Google) Code Review
14 changed files with 433 additions and 98 deletions

View File

@@ -2116,6 +2116,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);

View File

@@ -124,6 +124,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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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.
*
* <p> 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;
}

View File

@@ -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<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
@ShortcutType int shortcutType) {
final List<AccessibilityTarget> 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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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<TestAccessibilityShortcutChooserActivity> 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<TestAccessibilityShortcutChooserActivity> 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.
*/

View File

@@ -39,6 +39,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;
@@ -54,8 +55,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;
@@ -156,6 +159,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;
@@ -3871,6 +3875,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<String> 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;

View File

@@ -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<UserInfo> 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.
*

View File

@@ -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<UserInfo> 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);
}
}
}