diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 7d22e9a34836a..a0a00f7629b1b 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -20,10 +20,7 @@ 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.PermissionsUtils.enforceCallerPermissionsToRequest; import static com.android.server.companion.RolesUtils.isRoleHolder; import static java.util.Objects.requireNonNull; @@ -81,13 +78,13 @@ class AssociationRequestsProcessor { mService = service; final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO); - mServiceConnectors = new PerUser>() { + mServiceConnectors = new PerUser<>() { @Override protected ServiceConnector create(int userId) { return new ServiceConnector.Impl<>( - mContext, - serviceIntent, 0/* bindingFlags */, userId, - ICompanionDeviceDiscoveryService.Stub::asInterface); + mContext, + serviceIntent, 0/* bindingFlags */, userId, + ICompanionDeviceDiscoveryService.Stub::asInterface); } }; } @@ -99,6 +96,10 @@ class AssociationRequestsProcessor { void process(@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) { requireNonNull(request, "Request MUST NOT be null"); + if (request.isSelfManaged()) { + requireNonNull(request.getDisplayName(), "AssociationRequest.displayName " + + "MUST NOT be null."); + } requireNonNull(packageName, "Package name MUST NOT be null"); requireNonNull(callback, "Callback MUST NOT be null"); @@ -108,33 +109,33 @@ class AssociationRequestsProcessor { + "package=u" + userId + "/" + packageName); } - enforceCallerCanInteractWithUserId(mContext, userId); - enforceCallerIsSystemOr(userId, packageName); - + // 1. Enforce permissions and other requirements. + enforceCallerPermissionsToRequest(mContext, request, packageName, userId); mService.checkUsesFeature(packageName, userId); - if (request.isSelfManaged()) { - enforceRequestSelfManagedPermission(mContext); + // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER + // to perform discovery NOR to collect user consent). + if (request.isSelfManaged() && !request.isForceConfirmation() + && !willAddRoleHolder(request, packageName, userId)) { + // 2a. Create association right away. + final AssociationInfo association = mService.createAssociation(userId, packageName, + /* macAddress */ null, request.getDisplayName(), request.getDeviceProfile(), + /* selfManaged */true); + withCatchingRemoteException(() -> callback.onAssociationCreated(association)); + return; } - final String deviceProfile = request.getDeviceProfile(); - enforceRequestDeviceProfilePermissions(mContext, deviceProfile); - + // 2b. Launch the UI. synchronized (mService.mLock) { if (mRequest != null) { Slog.w(TAG, "CDM is already processing another AssociationRequest."); - try { - callback.onFailure("Busy."); - } catch (RemoteException e) { - // OK to ignore. - } - return; + withCatchingRemoteException(() -> callback.onFailure("Busy.")); } - try { - callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0); - } catch (RemoteException e) { + final boolean linked = withCatchingRemoteException( + () -> callback.asBinder().linkToDeath(mBinderDeathRecipient, 0)); + if (!linked) { // The process has died by now: do not proceed. return; } @@ -150,6 +151,7 @@ class AssociationRequestsProcessor { request.setSkipPrompt(true); } + final String deviceProfile = request.getDeviceProfile(); mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile) .thenComposeAsync(description -> { if (DEBUG) { @@ -171,7 +173,7 @@ class AssociationRequestsProcessor { }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> { if (err == null) { - mService.createAssociationInternal( + mService.legacyCreateAssociation( userId, deviceAddress, packageName, deviceProfile); mServiceConnectors.forUser(userId).post( ICompanionDeviceDiscoveryService::onAssociationCreated); @@ -183,6 +185,18 @@ class AssociationRequestsProcessor { })); } + private boolean willAddRoleHolder(@NonNull AssociationRequest request, + @NonNull String packageName, @UserIdInt int userId) { + final String deviceProfile = request.getDeviceProfile(); + if (deviceProfile == null) return false; + + final boolean isRoleHolder = Binder.withCleanCallingIdentity( + () -> isRoleHolder(mContext, userId, packageName, deviceProfile)); + + // Don't need to "grant" the role, if the package already holds the role. + return !isRoleHolder; + } + private void cleanup() { if (DEBUG) { Slog.d(TAG, "cleanup(); discovery = " @@ -315,4 +329,17 @@ class AssociationRequestsProcessor { return sameOemPackageCerts; } + + private static boolean withCatchingRemoteException(ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (RemoteException e) { + return false; + } + return true; + } + + private interface ThrowingRunnable { + void run() throws RemoteException; + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index f8be1656b3eed..dfc165e30e4aa 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -36,7 +36,7 @@ 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.checkCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; @@ -411,7 +411,7 @@ public class CompanionDeviceManagerService extends SystemService { + "permissions to get associations for u" + userId + "/" + packageName); } - if (!checkCallerCanManagerCompanionDevice(getContext())) { + if (!checkCallerCanManageCompanionDevice(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()); @@ -456,7 +456,7 @@ public class CompanionDeviceManagerService extends SystemService { if (association == null) { throw new IllegalArgumentException("Association does not exist " + "or the caller does not have permissions to manage it " - + "(ie. it belongs ot a different package or a different user)."); + + "(ie. it belongs to a different package or a different user)."); } disassociateInternal(userId, association.getId()); @@ -594,7 +594,7 @@ public class CompanionDeviceManagerService extends SystemService { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation"); - createAssociationInternal(userId, macAddress, packageName, null); + legacyCreateAssociation(userId, macAddress, packageName, null); } private void checkCanCallNotificationApi(String callingPackage) { @@ -671,21 +671,29 @@ public class CompanionDeviceManagerService extends SystemService { } } - void createAssociationInternal( - int userId, String deviceMacAddress, String packageName, String deviceProfile) { - final AssociationInfo association = new AssociationInfo( - getNewAssociationIdForPackage(userId, packageName), - userId, - packageName, - MacAddress.fromString(deviceMacAddress), - null, - deviceProfile, - /* managedByCompanionApp */false, - /* notifyOnDeviceNearby */ false , - System.currentTimeMillis()); + /** + * @deprecated use + * {@link #createAssociation(int, String, MacAddress, CharSequence, String, boolean)} + */ + @Deprecated + void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress, + @NonNull String packageName, @Nullable String deviceProfile) { + final MacAddress macAddress = MacAddress.fromString(deviceMacAddress); + createAssociation(userId, packageName, macAddress, null, deviceProfile, false); + } + + AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName, + @Nullable MacAddress macAddress, @Nullable CharSequence displayName, + @Nullable String deviceProfile, boolean selfManaged) { + final int id = getNewAssociationIdForPackage(userId, packageName); + final long timestamp = System.currentTimeMillis(); + final AssociationInfo association = new AssociationInfo(id, userId, packageName, + macAddress, displayName, deviceProfile, selfManaged, false, timestamp); updateSpecialAccessPermissionForAssociatedPackage(association); recordAssociation(association, userId); + + return association; } @GuardedBy("mLock") diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 3efbffc4d5a09..5cb30797c0f43 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -49,7 +49,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { int userId = getNextArgInt(); String packageName = getNextArgRequired(); String address = getNextArgRequired(); - mService.createAssociationInternal(userId, address, packageName, null); + mService.legacyCreateAssociation(userId, address, packageName, null); } break; diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java index a84bfa51f3edf..0d38fa3656419 100644 --- a/services/companion/java/com/android/server/companion/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java @@ -34,6 +34,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.content.Context; import android.os.RemoteException; @@ -64,6 +65,19 @@ final class PermissionsUtils { DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); } + static void enforceCallerPermissionsToRequest(@NonNull Context context, + @NonNull AssociationRequest request, @NonNull String packageName, + @UserIdInt int userId) { + enforceCallerCanInteractWithUserId(context, userId); + enforceCallerIsSystemOr(userId, packageName); + + enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile()); + + if (request.isSelfManaged()) { + enforceRequestSelfManagedPermission(context); + } + } + static void enforceRequestDeviceProfilePermissions( @NonNull Context context, @Nullable String deviceProfile) { // Device profile can be null. @@ -139,7 +153,7 @@ final class PermissionsUtils { } } - static boolean checkCallerCanManagerCompanionDevice(@NonNull Context context) { + static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { if (getCallingUserId() == SYSTEM_UID) return true; return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED; @@ -158,7 +172,7 @@ final class PermissionsUtils { if (!checkCallerCanInteractWithUserId(context, userId)) return false; - return checkCallerCanManagerCompanionDevice(context); + return checkCallerCanManageCompanionDevice(context); } private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {