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() {};
+}