Check REQUEST_COMPANION_SELF_MANAGED permission in CDM
Check if the caller holds REQUEST_COMPANION_SELF_MANAGED permission when
processing an AssociationRequest for a "self-managed" association in
CDM.
Introduce RolesUtils and PermissionsUtils utility classes.
Bug: 194301022
Test: atest CompanionDeviceManagerTest
Change-Id: Ia359f912fd28950a1251492f7e7b8452852008b6
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 184b06f..7d22e9a 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -16,24 +16,21 @@
package com.android.server.companion;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import static com.android.internal.util.CollectionUtils.filter;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
+import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.PermissionsUtils.enforceRequestDeviceProfilePermissions;
+import static com.android.server.companion.PermissionsUtils.enforceRequestSelfManagedPermission;
+import static com.android.server.companion.RolesUtils.isRoleHolder;
-import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -46,8 +43,6 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
import android.util.PackageUtils;
import android.util.Slog;
@@ -60,26 +55,12 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
class AssociationRequestsProcessor {
private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
- private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
- static {
- final Map<String, String> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
- map.put(DEVICE_PROFILE_APP_STREAMING,
- Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
- map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
- Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
-
- DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
- }
-
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
".CompanionDeviceDiscoveryService");
@@ -88,7 +69,6 @@
private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
private final Context mContext;
- private final RoleManager mRoleManager;
private final CompanionDeviceManagerService mService;
private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
@@ -96,10 +76,9 @@
private IAssociationRequestCallback mAppCallback;
private AndroidFuture<?> mOngoingDeviceDiscovery;
- AssociationRequestsProcessor(CompanionDeviceManagerService service, RoleManager roleManager) {
+ AssociationRequestsProcessor(CompanionDeviceManagerService service) {
mContext = service.getContext();
mService = service;
- mRoleManager = roleManager;
final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -129,13 +108,17 @@
+ "package=u" + userId + "/" + packageName);
}
- mService.enforceCallerCanInteractWithUserId(userId);
- mService.enforceCallerIsSystemOr(userId, packageName);
+ enforceCallerCanInteractWithUserId(mContext, userId);
+ enforceCallerIsSystemOr(userId, packageName);
mService.checkUsesFeature(packageName, userId);
+ if (request.isSelfManaged()) {
+ enforceRequestSelfManagedPermission(mContext);
+ }
+
final String deviceProfile = request.getDeviceProfile();
- validateDeviceProfileAndCheckPermission(deviceProfile);
+ enforceRequestDeviceProfilePermissions(mContext, deviceProfile);
synchronized (mService.mLock) {
if (mRequest != null) {
@@ -190,9 +173,8 @@
if (err == null) {
mService.createAssociationInternal(
userId, deviceAddress, packageName, deviceProfile);
- mServiceConnectors.forUser(userId).post(service -> {
- service.onAssociationCreated();
- });
+ mServiceConnectors.forUser(userId).post(
+ ICompanionDeviceDiscoveryService::onAssociationCreated);
} else {
Slog.e(TAG, "Failed to discover device(s)", err);
callback.onFailure("No devices found: " + err.getMessage());
@@ -201,43 +183,6 @@
}));
}
- private boolean isRoleHolder(int userId, String packageName, String role) {
- final long identity = Binder.clearCallingIdentity();
- try {
- List<String> holders = mRoleManager.getRoleHoldersAsUser(role, UserHandle.of(userId));
- return holders.contains(packageName);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
- // Device profile can be null.
- if (deviceProfile == null) return;
-
- if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
- // TODO: remove, when properly supporting this profile.
- throw new UnsupportedOperationException(
- "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
- }
-
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- // TODO: remove, when properly supporting this profile.
- throw new UnsupportedOperationException(
- "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
- }
-
- if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
- throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
- }
-
- final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
- if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
- throw new SecurityException("Application must hold " + permission + " to associate "
- + "with a device with " + deviceProfile + " profile.");
- }
- }
-
private void cleanup() {
if (DEBUG) {
Slog.d(TAG, "cleanup(); discovery = "
@@ -257,10 +202,14 @@
}
private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
- if (mRequest.getDeviceProfile() != null
- && isRoleHolder(userId, packageName, mRequest.getDeviceProfile())) {
- // Don't need to collect user's consent since app already holds the role.
- return true;
+ final String deviceProfile = mRequest.getDeviceProfile();
+ if (deviceProfile != null) {
+ final boolean isRoleHolder = Binder.withCleanCallingIdentity(
+ () -> isRoleHolder(mContext, userId, packageName, deviceProfile));
+ if (isRoleHolder) {
+ // Don't need to collect user's consent since app already holds the role.
+ return true;
+ }
}
String[] sameOemPackages = mContext.getResources()
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6c39862..316807c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,15 +17,12 @@
package com.android.server.companion;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Binder.getCallingUid;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.getCallingUserId;
@@ -38,6 +35,13 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.checkCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
+import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
@@ -52,7 +56,6 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
@@ -162,7 +165,6 @@
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private PowerWhitelistManager mPowerWhitelistManager;
private IAppOpsService mAppOpsManager;
- private RoleManager mRoleManager;
private BluetoothAdapter mBluetoothAdapter;
private UserManager mUserManager;
@@ -205,7 +207,6 @@
mPersistentDataStore = new PersistentDataStore();
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
- mRoleManager = context.getSystemService(RoleManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
@@ -215,7 +216,7 @@
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
mCompanionDevicePresenceController = new CompanionDevicePresenceController();
- mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mRoleManager);
+ mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
registerPackageMonitor();
}
@@ -328,8 +329,9 @@
final int userId = association.getUserId();
final String packageName = association.getPackageName();
-
- if (!checkCallerCanManageAssociationsForPackage(userId, packageName)) return null;
+ if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
+ return null;
+ }
return association;
}
@@ -397,18 +399,17 @@
+ "request=" + request + ", "
+ "package=u" + userId + "/" + packageName);
mAssociationRequestsProcessor.process(request, packageName, userId, callback);
-
}
@Override
public List<AssociationInfo> getAssociations(String packageName, int userId) {
final int callingUid = getCallingUserId();
- if (!checkCallerCanManageAssociationsForPackage(userId, packageName)) {
+ if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
+ "permissions to get associations for u" + userId + "/" + packageName);
}
- if (!checkCallerCanManagerCompanionDevice()) {
+ if (!checkCallerCanManagerCompanionDevice(getContext())) {
// If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
// request the feature (also: the caller is the app itself).
checkUsesFeature(packageName, getCallingUserId());
@@ -420,8 +421,8 @@
@Override
public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
- enforceCallerCanInteractWithUserId(userId);
- enforceCallerCanManagerCompanionDevice();
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");
return new ArrayList<>(
CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
@@ -430,8 +431,9 @@
@Override
public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
int userId) {
- enforceCallerCanInteractWithUserId(userId);
- enforceCallerCanManagerCompanionDevice();
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(),
+ "addOnAssociationsChangedListener");
//TODO: Implement.
}
@@ -617,7 +619,7 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
- enforceCallerCanManagerCompanionDevice();
+ enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand");
new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -767,25 +769,8 @@
+ " for " + association
+ " - profile still present in " + otherAssociationWithDeviceProfile);
} else {
- final long identity = Binder.clearCallingIdentity();
- try {
- mRoleManager.removeRoleHolderAsUser(
- association.getDeviceProfile(),
- association.getPackageName(),
- RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
- UserHandle.of(association.getUserId()),
- getContext().getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(LOG_TAG, "Failed to revoke device profile role "
- + association.getDeviceProfile()
- + " to " + association.getPackageName()
- + " for user " + association.getUserId());
- }
- });
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ Binder.withCleanCallingIdentity(
+ () -> removeRoleHolderForAssociation(getContext(), association));
}
}
}
@@ -835,7 +820,7 @@
if (!association.isSelfManaged()) {
if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) {
- grantDeviceProfile(association);
+ addRoleHolderForAssociation(getContext(), association);
}
if (association.isNotifyOnDeviceNearby()) {
@@ -957,7 +942,8 @@
Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+ " to " + association.getPackageName()
+ " due to device connected: " + association.getDeviceMacAddress());
- grantDeviceProfile(association);
+
+ addRoleHolderForAssociation(getContext(), association);
}
}
}
@@ -966,27 +952,6 @@
onDeviceNearby(address);
}
- private void grantDeviceProfile(AssociationInfo association) {
- Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
-
- if (association.getDeviceProfile() != null) {
- mRoleManager.addRoleHolderAsUser(
- association.getDeviceProfile(),
- association.getPackageName(),
- RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
- UserHandle.of(association.getUserId()),
- getContext().getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(LOG_TAG, "Failed to grant device profile role "
- + association.getDeviceProfile()
- + " to " + association.getPackageName()
- + " for user " + association.getUserId());
- }
- });
- }
- }
-
void onDeviceDisconnected(String address) {
Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")");
@@ -1281,74 +1246,6 @@
return copy;
}
- boolean checkCallerCanInteractWithUserId(int userId) {
- if (getCallingUserId() == userId) return true;
-
- return getContext().checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
- }
-
- void enforceCallerCanInteractWithUserId(int userId) {
- if (getCallingUserId() == userId) return;
-
- getContext().enforceCallingPermission(INTERACT_ACROSS_USERS, null);
- }
-
- private boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String pkg) {
- final int callingUid = getCallingUid();
- if (callingUid == SYSTEM_UID) return true;
-
- if (getCallingUserId() != userId) return false;
-
- try {
- if (mAppOpsManager.checkPackage(callingUid, pkg) != MODE_ALLOWED) return false;
- } catch (RemoteException e) {
- // Can't happen: AppOpsManager is running in the same process.
- }
- return true;
- }
-
- void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String pkg) {
- final int callingUid = getCallingUid();
- if (callingUid == SYSTEM_UID) return;
-
- final int callingUserId = getCallingUserId();
- if (getCallingUserId() != userId) {
- throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
- + "the expected UserId (" + userId + ")");
- }
-
- try {
- if (mAppOpsManager.checkPackage(callingUid, pkg) != MODE_ALLOWED) {
- throw new SecurityException(pkg + " doesn't belong to calling uid ("
- + callingUid + ")");
- }
- } catch (RemoteException e) {
- // Can't happen: AppOpsManager is running in the same process.
- }
- }
-
- private boolean checkCallerCanManagerCompanionDevice() {
- if (getCallingUserId() == SYSTEM_UID) return true;
-
- return getContext().checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
- }
-
- void enforceCallerCanManagerCompanionDevice() {
- if (getCallingUserId() == SYSTEM_UID) return;
-
- getContext().enforceCallingPermission(MANAGE_COMPANION_DEVICES,
- "Caller must hold " + MANAGE_COMPANION_DEVICES + " permission.");
- }
-
- private boolean checkCallerCanManageAssociationsForPackage(
- @UserIdInt int userId, @NonNull String packageName) {
- if (checkCallerIsSystemOr(userId, packageName)) return true;
-
- if (!checkCallerCanInteractWithUserId(userId)) return false;
-
- return checkCallerCanManagerCompanionDevice();
- }
-
void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
if (getCallingUserId() == SYSTEM_UID) return;
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
new file mode 100644
index 0000000..a84bfa51
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 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.companion;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Binder.getCallingUid;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.getCallingUserId;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+
+import com.android.internal.app.IAppOpsService;
+
+import java.util.Map;
+
+/**
+ * Utility methods for checking permissions required for accessing {@link CompanionDeviceManager}
+ * APIs (such as {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH},
+ * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING},
+ * {@link Manifest.permission#REQUEST_COMPANION_SELF_MANAGED} etc.)
+ */
+final class PermissionsUtils {
+
+ private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
+ static {
+ final Map<String, String> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+ map.put(DEVICE_PROFILE_APP_STREAMING,
+ Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
+ map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
+ Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
+
+ DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
+ }
+
+ static void enforceRequestDeviceProfilePermissions(
+ @NonNull Context context, @Nullable String deviceProfile) {
+ // Device profile can be null.
+ if (deviceProfile == null) return;
+
+ if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
+ throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
+ }
+
+ if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
+ // TODO: remove, when properly supporting this profile.
+ throw new UnsupportedOperationException(
+ "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
+ }
+
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ // TODO: remove, when properly supporting this profile.
+ throw new UnsupportedOperationException(
+ "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
+ }
+
+ final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
+ if (context.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+ throw new SecurityException("Application must hold " + permission + " to associate "
+ + "with a device with " + deviceProfile + " profile.");
+ }
+ }
+
+ static void enforceRequestSelfManagedPermission(@NonNull Context context) {
+ if (context.checkCallingOrSelfPermission(REQUEST_COMPANION_SELF_MANAGED)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Application does not hold "
+ + REQUEST_COMPANION_SELF_MANAGED);
+ }
+ }
+
+ static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ if (getCallingUserId() == userId) return true;
+
+ return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
+ }
+
+ static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ if (getCallingUserId() == userId) return;
+
+ context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
+ }
+
+ static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return true;
+
+ if (getCallingUserId() != userId) return false;
+
+ if (!checkPackage(callingUid, packageName)) return false;
+
+ return true;
+ }
+
+ static void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return;
+
+ final int callingUserId = getCallingUserId();
+ if (getCallingUserId() != userId) {
+ throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
+ + "the expected UserId (" + userId + ")");
+ }
+
+ if (!checkPackage(callingUid, packageName)) {
+ throw new SecurityException(packageName + " doesn't belong to calling uid ("
+ + callingUid + ")");
+ }
+ }
+
+ static boolean checkCallerCanManagerCompanionDevice(@NonNull Context context) {
+ if (getCallingUserId() == SYSTEM_UID) return true;
+
+ return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
+ }
+
+ static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
+ @Nullable String message) {
+ if (getCallingUserId() == SYSTEM_UID) return;
+
+ context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
+ }
+
+ static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ if (checkCallerIsSystemOr(userId, packageName)) return true;
+
+ if (!checkCallerCanInteractWithUserId(context, userId)) return false;
+
+ return checkCallerCanManagerCompanionDevice(context);
+ }
+
+ private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
+ try {
+ return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Can't happen: AppOpsManager is running in the same process.
+ return true;
+ }
+ }
+
+ private static IAppOpsService getAppOpsService() {
+ if (sAppOpsService == null) {
+ synchronized (PermissionsUtils.class) {
+ if (sAppOpsService == null) {
+ sAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+ }
+ }
+ return sAppOpsService;
+ }
+
+ // DO NOT USE DIRECTLY! Access via getAppOpsService().
+ private static IAppOpsService sAppOpsService = null;
+
+ private PermissionsUtils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
new file mode 100644
index 0000000..76340fc
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.companion;
+
+import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
+
+import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
+import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.role.RoleManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.util.List;
+
+/** Utility methods for accessing {@link RoleManager} APIs. */
+final class RolesUtils {
+
+ static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
+ @NonNull String packageName, @NonNull String role) {
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+ final List<String> roleHolders = roleManager.getRoleHoldersAsUser(
+ role, UserHandle.of(userId));
+ return roleHolders.contains(packageName);
+ }
+
+ static void addRoleHolderForAssociation(
+ @NonNull Context context, @NonNull AssociationInfo associationInfo) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
+ }
+
+ final String deviceProfile = associationInfo.getDeviceProfile();
+ if (deviceProfile == null) return;
+
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+
+ final String packageName = associationInfo.getPackageName();
+ final int userId = associationInfo.getUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
+
+ roleManager.addRoleHolderAsUser(deviceProfile, packageName,
+ MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+ success -> {
+ if (!success) {
+ Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName
+ + " to the list of " + deviceProfile + " holders.");
+ }
+ });
+ }
+
+ static void removeRoleHolderForAssociation(
+ @NonNull Context context, @NonNull AssociationInfo associationInfo) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
+ }
+
+ final String deviceProfile = associationInfo.getDeviceProfile();
+ if (deviceProfile == null) return;
+
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+
+ final String packageName = associationInfo.getPackageName();
+ final int userId = associationInfo.getUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
+
+ roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
+ MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+ success -> {
+ if (!success) {
+ Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName
+ + " from the list of " + deviceProfile + " holders.");
+ }
+ });
+ }
+
+ private RolesUtils() {};
+}