Merge "Fix A11y features blocked by IT admin can be bypassed using a11y shortcuts" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
46be022182
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user