blob: f45fa921c4a2af0d30acbf49d8b7b2a11a9e486c [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_BY_ADMIN;
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.role.RoleManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.util.ArrayUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import libcore.util.EmptyArray;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* This class provides APIs of accessibility security policies for accessibility manager
* to grant accessibility capabilities or events access right to accessibility services. And also
* monitors the current bound accessibility services to prompt permission warnings for
* not accessibility-categorized ones.
*/
public class AccessibilitySecurityPolicy {
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
private static final String LOG_TAG = "AccessibilitySecurityPolicy";
private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED
| AccessibilityEvent.TYPE_VIEW_FOCUSED
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
| AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
| AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
| AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
| AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
| AccessibilityEvent.TYPE_WINDOWS_CHANGED
| AccessibilityEvent.TYPE_VIEW_SELECTED
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
| AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
| AccessibilityEvent.TYPE_VIEW_SCROLLED
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
| AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
| AccessibilityEvent.TYPE_VIEW_TARGETED_BY_SCROLL;
/**
* Methods that should find their way into separate modules, but are still in AMS
* TODO (b/111889696): Refactoring UserState to AccessibilityUserManager.
*/
public interface AccessibilityUserManager {
/**
* Returns current userId maintained in accessibility manager service
*/
int getCurrentUserIdLocked();
// TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
// TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
/**
* Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
*/
@Nullable SparseBooleanArray getVisibleUserIdsLocked();
}
private final Context mContext;
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final UserManager mUserManager;
private final AppOpsManager mAppOpsManager;
private final AccessibilityUserManager mAccessibilityUserManager;
private final PolicyWarningUIController mPolicyWarningUIController;
/** All bound accessibility services which don't belong to accessibility category. */
private final ArraySet<ComponentName> mNonA11yCategoryServices = new ArraySet<>();
private AppWidgetManagerInternal mAppWidgetService;
private AccessibilityWindowManager mAccessibilityWindowManager;
private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mSendNonA11yToolNotificationEnabled = false;
/**
* Constructor for AccessibilityManagerService.
*/
public AccessibilitySecurityPolicy(PolicyWarningUIController policyWarningUIController,
@NonNull Context context,
@NonNull AccessibilityUserManager a11yUserManager,
@NonNull PackageManagerInternal packageManagerInternal) {
mContext = context;
mAccessibilityUserManager = a11yUserManager;
mPackageManager = mContext.getPackageManager();
mPackageManagerInternal = packageManagerInternal;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mPolicyWarningUIController = policyWarningUIController;
}
/**
* Enables sending the notification for non-AccessibilityTool services with the given state.
*
*/
public void setSendingNonA11yToolNotificationLocked(boolean enable) {
if (enable == mSendNonA11yToolNotificationEnabled) {
return;
}
mSendNonA11yToolNotificationEnabled = enable;
mPolicyWarningUIController.enableSendingNonA11yToolNotification(enable);
if (enable) {
for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
final ComponentName service = mNonA11yCategoryServices.valueAt(i);
mPolicyWarningUIController.onNonA11yCategoryServiceBound(mCurrentUserId, service);
}
}
}
/**
* Setup AccessibilityWindowManager. This isn't part of the constructor because the
* window manager and security policy both call each other.
*/
public void setAccessibilityWindowManager(@NonNull AccessibilityWindowManager awm) {
mAccessibilityWindowManager = awm;
}
/**
* Setup AppWidgetManger during boot phase.
*/
public void setAppWidgetManager(@NonNull AppWidgetManagerInternal appWidgetManager) {
mAppWidgetService = appWidgetManager;
}
/**
* Check if an accessibility event can be dispatched. Events should be dispatched only if they
* are dispatched from items that services can see.
*
* @param userId The userId to check
* @param event The event to check
* @return {@code true} if the event can be dispatched
*/
public boolean canDispatchAccessibilityEventLocked(int userId,
@NonNull AccessibilityEvent event) {
final int eventType = event.getEventType();
switch (eventType) {
// All events that are for changes in a global window
// state should *always* be dispatched.
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
case AccessibilityEvent.TYPE_ANNOUNCEMENT:
// All events generated by the user touching the
// screen should *always* be dispatched.
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
// Also always dispatch the event that assist is reading context.
case AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT:
// Also windows changing should always be dispatched.
case AccessibilityEvent.TYPE_WINDOWS_CHANGED: {
return true;
}
// All events for changes in window content should be
// dispatched *only* if this window is one of the windows
// the accessibility layer reports which are windows
// that a sighted user can touch.
default: {
return isRetrievalAllowingWindowLocked(userId, event.getWindowId());
}
}
}
/**
* Find a valid package name for an app to expose to accessibility
*
* @param packageName The package name the app wants to expose
* @param appId The app's id
* @param userId The app's user id
* @param pid The app's process pid that requested this
* @return A package name that is valid to report
*/
@Nullable
public String resolveValidReportedPackageLocked(
@Nullable CharSequence packageName, int appId, int userId, int pid) {
// Okay to pass no package
if (packageName == null) {
return null;
}
// The system gets to pass any package
if (appId == Process.SYSTEM_UID) {
return packageName.toString();
}
// Passing a package in your UID is fine
final String packageNameStr = packageName.toString();
final int resolvedUid = UserHandle.getUid(userId, appId);
if (isValidPackageForUid(packageNameStr, resolvedUid)) {
return packageName.toString();
}
// Appwidget hosts get to pass packages for widgets they host
if (mAppWidgetService != null && ArrayUtils.contains(mAppWidgetService
.getHostedWidgetPackages(resolvedUid), packageNameStr)) {
return packageName.toString();
}
// If app has the targeted permission to act as another package
if (mContext.checkPermission(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
pid, resolvedUid) == PackageManager.PERMISSION_GRANTED) {
return packageName.toString();
}
// Otherwise, set the package to the first one in the UID
final String[] packageNames = mPackageManager.getPackagesForUid(resolvedUid);
if (ArrayUtils.isEmpty(packageNames)) {
return null;
}
// Okay, the caller reported a package it does not have access to.
// Instead of crashing the caller for better backwards compatibility
// we report the first package in the UID. Since most of the time apps
// don't use shared user id, this will yield correct results and for
// the edge case of using a shared user id we may report the wrong
// package but this is fine since first, this is a cheating app and
// second there is no way to get the correct package anyway.
return packageNames[0];
}
/**
* Get the packages that are valid for a uid. In some situations, like app widgets, there
* could be several valid packages
*
* @param targetPackage A package that is known to be valid for this id
* @param targetUid The whose packages should be checked
* @return An array of all valid package names. An empty array means any package is OK
*/
@NonNull
public String[] computeValidReportedPackages(
@NonNull String targetPackage, int targetUid) {
if (UserHandle.getAppId(targetUid) == Process.SYSTEM_UID) {
// Empty array means any package is Okay
return EmptyArray.STRING;
}
// IMPORTANT: The target package is already vetted to be in the target UID
String[] uidPackages = new String[]{targetPackage};
// Appwidget hosts get to pass packages for widgets they host
if (mAppWidgetService != null) {
final ArraySet<String> widgetPackages = mAppWidgetService
.getHostedWidgetPackages(targetUid);
if (widgetPackages != null && !widgetPackages.isEmpty()) {
final String[] validPackages = new String[uidPackages.length
+ widgetPackages.size()];
System.arraycopy(uidPackages, 0, validPackages, 0, uidPackages.length);
final int widgetPackageCount = widgetPackages.size();
for (int i = 0; i < widgetPackageCount; i++) {
validPackages[uidPackages.length + i] = widgetPackages.valueAt(i);
}
return validPackages;
}
}
return uidPackages;
}
/**
* Reset the event source for events that should not carry one
*
* @param event The event potentially to modify
*/
public void updateEventSourceLocked(@NonNull AccessibilityEvent event) {
if ((event.getEventType() & KEEP_SOURCE_EVENT_TYPES) == 0) {
event.setSource(null);
}
}
/**
* Check if a service can have access to a window
*
* @param userId The id of the user running the service
* @param service The service requesting access
* @param windowId The window it wants access to
*
* @return Whether ot not the service may retrieve info from the window
*/
public boolean canGetAccessibilityNodeInfoLocked(int userId,
@NonNull AbstractAccessibilityServiceConnection service, int windowId) {
return canRetrieveWindowContentLocked(service)
&& isRetrievalAllowingWindowLocked(userId, windowId);
}
/**
* Check if a service can have access the list of windows
*
* @param service The service requesting access
*
* @return Whether ot not the service may retrieve the window list
*/
public boolean canRetrieveWindowsLocked(
@NonNull AbstractAccessibilityServiceConnection service) {
return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
}
/**
* Check if a service can have access the content of windows on the screen
*
* @param service The service requesting access
*
* @return Whether ot not the service may retrieve the content
*/
public boolean canRetrieveWindowContentLocked(
@NonNull AbstractAccessibilityServiceConnection service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
/**
* Check if a service can control magnification
*
* @param service The service requesting access
*
* @return Whether ot not the service may control magnification
*/
public boolean canControlMagnification(
@NonNull AbstractAccessibilityServiceConnection service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
/**
* Check if a service can perform gestures
*
* @param service The service requesting access
*
* @return Whether ot not the service may perform gestures
*/
public boolean canPerformGestures(@NonNull AccessibilityServiceConnection service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0;
}
/**
* Check if a service can capture gestures from the fingerprint sensor
*
* @param service The service requesting access
*
* @return Whether ot not the service may capture gestures from the fingerprint sensor
*/
public boolean canCaptureFingerprintGestures(@NonNull AccessibilityServiceConnection service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES) != 0;
}
/**
* Checks if a service can take screenshot.
*
* @param service The service requesting access
*
* @return Whether ot not the service may take screenshot
*/
public boolean canTakeScreenshotLocked(
@NonNull AbstractAccessibilityServiceConnection service) {
return (service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
}
/**
* Check whether the input method can be enabled or disabled by the accessibility service.
*
* @param imeId The id of the input method.
* @param service The accessibility service connection.
* @return Whether the input method can be enabled/disabled or the reason why it can't be
* enabled/disabled.
* @throws SecurityException if the input method is not in the same package as the service.
*/
@AccessibilityService.SoftKeyboardController.EnableImeResult
int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service)
throws SecurityException {
final String servicePackageName = service.getComponentName().getPackageName();
final int callingUserId = UserHandle.getCallingUserId();
InputMethodInfo inputMethodInfo = null;
List<InputMethodInfo> inputMethodInfoList =
InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
if (inputMethodInfoList != null) {
for (InputMethodInfo info : inputMethodInfoList) {
if (info.getId().equals(imeId)) {
inputMethodInfo = info;
break;
}
}
}
if (inputMethodInfo == null
|| !inputMethodInfo.getPackageName().equals(servicePackageName)) {
throw new SecurityException("The input method is in a different package with the "
+ "accessibility service");
}
// TODO(b/207697949, b/208872785): Add cts test for managed device.
// Use RestrictedLockUtilsInternal in AccessibilitySecurityPolicy
if (RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
mContext, inputMethodInfo.getPackageName(), callingUserId) != null) {
return ENABLE_IME_FAIL_BY_ADMIN;
}
return ENABLE_IME_SUCCESS;
}
/**
* Returns the parent userId of the profile according to the specified userId.
*
* @param userId The userId to check
* @return the parent userId of the profile, or self if no parent exist
*/
public int resolveProfileParentLocked(int userId) {
if (userId != mAccessibilityUserManager.getCurrentUserIdLocked()) {
final long identity = Binder.clearCallingIdentity();
try {
UserInfo parent = mUserManager.getProfileParent(userId);
if (parent != null) {
return parent.getUserHandle().getIdentifier();
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
return userId;
}
/**
* Returns the parent userId of the profile according to the specified userId. Enforcing
* permissions check if specified userId is not caller's userId.
*
* @param userId The userId to check
* @return the parent userId of the profile, or self if no parent exist
* @throws SecurityException if caller cannot interact across users
* @throws IllegalArgumentException if specified invalid userId
*/
public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) {
final int callingUid = Binder.getCallingUid();
final int currentUserId = mAccessibilityUserManager.getCurrentUserIdLocked();
if (callingUid == 0
|| callingUid == Process.SYSTEM_UID
|| callingUid == Process.SHELL_UID) {
if (userId == UserHandle.USER_CURRENT
|| userId == UserHandle.USER_CURRENT_OR_SELF) {
return currentUserId;
}
return resolveProfileParentLocked(userId);
}
final int callingUserId = UserHandle.getUserId(callingUid);
if (callingUserId == userId) {
return resolveProfileParentLocked(userId);
}
final int callingUserParentId = resolveProfileParentLocked(callingUserId);
if (callingUserParentId == currentUserId && (userId == UserHandle.USER_CURRENT
|| userId == UserHandle.USER_CURRENT_OR_SELF)) {
return currentUserId;
}
if (!hasPermission(Manifest.permission.INTERACT_ACROSS_USERS)
&& !hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) {
throw new SecurityException("Call from user " + callingUserId + " as user "
+ userId + " without permission INTERACT_ACROSS_USERS or "
+ "INTERACT_ACROSS_USERS_FULL not allowed.");
}
if (userId == UserHandle.USER_CURRENT
|| userId == UserHandle.USER_CURRENT_OR_SELF) {
return currentUserId;
}
return resolveProfileParentLocked(userId);
}
/**
* Returns false if caller is not SYSTEM and SHELL, and tried to interact across users.
*
* @param userId The userId to interact.
* @return false if caller cannot interact across users.
*/
public boolean isCallerInteractingAcrossUsers(int userId) {
final int callingUid = Binder.getCallingUid();
return (Binder.getCallingPid() == android.os.Process.myPid()
|| callingUid == Process.SHELL_UID
|| userId == UserHandle.USER_CURRENT
|| userId == UserHandle.USER_CURRENT_OR_SELF);
}
private boolean isValidPackageForUid(String packageName, int uid) {
final long token = Binder.clearCallingIdentity();
try {
// Since we treat calls from a profile as if made by its parent, using
// MATCH_ANY_USER to query the uid of the given package name.
return mPackageManagerInternal.isSameApp(packageName, PackageManager.MATCH_ANY_USER,
uid, UserHandle.getUserId(uid));
} finally {
Binder.restoreCallingIdentity(token);
}
}
private boolean isRetrievalAllowingWindowLocked(int userId, int windowId) {
// The system gets to interact with any window it wants.
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
return true;
}
if (Binder.getCallingUid() == Process.SHELL_UID) {
if (!isShellAllowedToRetrieveWindowLocked(userId, windowId)) {
return false;
}
}
if (mAccessibilityWindowManager.resolveParentWindowIdLocked(windowId)
== mAccessibilityWindowManager.getActiveWindowId(userId)) {
return true;
}
return mAccessibilityWindowManager.findA11yWindowInfoByIdLocked(windowId) != null;
}
private boolean isShellAllowedToRetrieveWindowLocked(int userId, int windowId) {
final long token = Binder.clearCallingIdentity();
try {
IBinder windowToken = mAccessibilityWindowManager
.getWindowTokenForUserAndWindowIdLocked(userId, windowId);
if (windowToken == null) {
return false;
}
int windowOwnerUserId = mAccessibilityWindowManager.getWindowOwnerUserId(windowToken);
if (windowOwnerUserId == UserHandle.USER_NULL) {
return false;
}
return !mUserManager.hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(windowOwnerUserId));
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Enforcing permission check to caller.
*
* @param permission The permission to check
* @param function The function name to check
*/
public void enforceCallingPermission(@NonNull String permission, @Nullable String function) {
if (OWN_PROCESS_ID == Binder.getCallingPid()) {
return;
}
if (!hasPermission(permission)) {
throw new SecurityException("You do not have " + permission
+ " required to call " + function + " from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
}
}
/**
* Permission check to caller.
*
* @param permission The permission to check
* @return true if caller has permission
*/
public boolean hasPermission(@NonNull String permission) {
return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* Checks if accessibility service could register into the system.
*
* @param serviceInfo The ServiceInfo
* @return True if it could register into the system
*/
public boolean canRegisterService(@NonNull ServiceInfo serviceInfo) {
if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals(
serviceInfo.permission)) {
Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
serviceInfo.packageName, serviceInfo.name).flattenToShortString()
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE);
return false;
}
if ((serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
serviceInfo.packageName, serviceInfo.name).flattenToShortString()
+ ": the service is the external one and doesn't allow to register as "
+ "an accessibility service ");
return false;
}
int servicePackageUid = serviceInfo.applicationInfo.uid;
if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
servicePackageUid, serviceInfo.packageName, null, null)
!= AppOpsManager.MODE_ALLOWED) {
Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
serviceInfo.packageName, serviceInfo.name).flattenToShortString()
+ ": disallowed by AppOps");
return false;
}
return true;
}
/**
* Checks if accessibility service could execute accessibility operations.
*
* @param service The accessibility service connection
* @return True if it could execute accessibility operations
*/
public boolean checkAccessibilityAccess(AbstractAccessibilityServiceConnection service) {
final String packageName = service.getComponentName().getPackageName();
final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
if (resolveInfo == null) {
// For InteractionBridge, UiAutomation, and Proxy.
return true;
}
final int servicePackageUid = resolveInfo.serviceInfo.applicationInfo.uid;
final int callingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
final String attributionTag = service.getAttributionTag();
try {
// For the caller is system, just block the data to a11y services.
if (OWN_PROCESS_ID == callingPid) {
return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY,
servicePackageUid, packageName, attributionTag, null)
== AppOpsManager.MODE_ALLOWED;
}
return mAppOpsManager.noteOp(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY,
servicePackageUid, packageName, attributionTag, null)
== AppOpsManager.MODE_ALLOWED;
} finally {
Binder.restoreCallingIdentity(identityToken);
}
}
/**
* Enforcing permission check to IPC caller or grant it if it's not through IPC.
*
* @param permission The permission to check
*/
public void enforceCallingOrSelfPermission(@NonNull String permission) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ permission);
}
}
/**
* Throws a SecurityException if the caller has neither the MANAGE_ACCESSIBILITY permission nor
* the COMPANION_DEVICE_APP_STREAMING role.
*/
public void checkForAccessibilityPermissionOrRole() {
final boolean canManageAccessibility =
mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
== PackageManager.PERMISSION_GRANTED;
if (canManageAccessibility) {
return;
}
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
final RoleManager roleManager = mContext.getSystemService(RoleManager.class);
if (roleManager != null) {
final List<String> holders = roleManager.getRoleHoldersAsUser(
DEVICE_PROFILE_APP_STREAMING, UserHandle.getUserHandleForUid(callingUid));
final String[] packageNames = mPackageManager.getPackagesForUid(callingUid);
if (packageNames != null) {
for (String packageName : packageNames) {
if (holders.contains(packageName)) {
return;
}
}
}
}
throw new SecurityException(
"Cannot register a proxy for a device without the "
+ "android.app.role.COMPANION_DEVICE_APP_STREAMING role or the"
+ " MANAGE_ACCESSIBILITY permission.");
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Called after a service was bound or unbound. Checks the current bound accessibility
* services and updates alarms.
*
* @param userId The user id
* @param boundServices The bound services
*/
public void onBoundServicesChangedLocked(int userId,
ArrayList<AccessibilityServiceConnection> boundServices) {
if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
return;
}
ArraySet<ComponentName> tempNonA11yCategoryServices = new ArraySet<>();
for (int i = 0; i < boundServices.size(); i++) {
final AccessibilityServiceInfo a11yServiceInfo = boundServices.get(
i).getServiceInfo();
final ComponentName service = a11yServiceInfo.getComponentName().clone();
if (!a11yServiceInfo.isAccessibilityTool()) {
tempNonA11yCategoryServices.add(service);
if (mNonA11yCategoryServices.contains(service)) {
mNonA11yCategoryServices.remove(service);
} else {
if (mSendNonA11yToolNotificationEnabled) {
mPolicyWarningUIController.onNonA11yCategoryServiceBound(userId, service);
}
}
}
}
for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
final ComponentName service = mNonA11yCategoryServices.valueAt(i);
mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(userId, service);
}
mNonA11yCategoryServices.clear();
mNonA11yCategoryServices.addAll(tempNonA11yCategoryServices);
}
/**
* Called after switching to another user. Resets data and cancels old alarms after
* switching to another user.
*
* @param userId The user id
* @param enabledServices The enabled services
*/
public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) {
if (mCurrentUserId == userId) {
return;
}
mPolicyWarningUIController.onSwitchUser(userId,
new ArraySet<>(enabledServices));
for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(mCurrentUserId,
mNonA11yCategoryServices.valueAt(i));
}
mNonA11yCategoryServices.clear();
mCurrentUserId = userId;
}
/**
* Called after the enabled accessibility services changed.
*
* @param userId The user id
* @param enabledServices The enabled services
*/
public void onEnabledServicesChangedLocked(int userId, Set<ComponentName> enabledServices) {
if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
return;
}
mPolicyWarningUIController.onEnabledServicesChanged(userId,
new ArraySet<>(enabledServices));
}
}