Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 64122384 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski Committed by Android (Google) Code Review
Browse files

Merge "Allow for CDM associations w/o role grants." into main

parents 79d02565 bdded825
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -909,6 +909,14 @@ package android.companion {
    method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
  }

  public final class AssociationRequest implements android.os.Parcelable {
    method public boolean isSkipRoleGrant();
  }

  public static final class AssociationRequest.Builder {
    method @NonNull @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public android.companion.AssociationRequest.Builder setSkipRoleGrant(boolean);
  }

  public final class CompanionDeviceManager {
    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
  }
+53 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.companion;

import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;

import static com.android.internal.util.CollectionUtils.emptyIfNull;
@@ -28,7 +29,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Icon;
import android.os.Build;
@@ -213,6 +217,11 @@ public final class AssociationRequest implements Parcelable {
     */
    private final boolean mForceConfirmation;

    /**
     * Whether to skip the role grant, permission checks and consent dialog.
     */
    private final boolean mSkipRoleGrant;

    /**
     * The app package name of the application the association will belong to.
     * Populated by the system.
@@ -283,6 +292,7 @@ public final class AssociationRequest implements Parcelable {
            @Nullable CharSequence displayName,
            boolean selfManaged,
            boolean forceConfirmation,
            boolean skipRoleGrant,
            @Nullable Icon deviceIcon) {
        mSingleDevice = singleDevice;
        mDeviceFilters = requireNonNull(deviceFilters);
@@ -290,6 +300,7 @@ public final class AssociationRequest implements Parcelable {
        mDisplayName = displayName;
        mSelfManaged = selfManaged;
        mForceConfirmation = forceConfirmation;
        mSkipRoleGrant = skipRoleGrant;
        mCreationTime = System.currentTimeMillis();
        mDeviceIcon = deviceIcon;
    }
@@ -332,6 +343,18 @@ public final class AssociationRequest implements Parcelable {
        return mForceConfirmation;
    }

    /**
     * Whether to skip the role grant, permission checks and consent dialog.
     *
     * @see Builder#setSkipRoleGrant(boolean)
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public boolean isSkipRoleGrant() {
        return mSkipRoleGrant;
    }

    /**
     * Whether only a single device should match the provided filter.
     *
@@ -407,6 +430,7 @@ public final class AssociationRequest implements Parcelable {
        private CharSequence mDisplayName;
        private boolean mSelfManaged = false;
        private boolean mForceConfirmation = false;
        private boolean mSkipRoleGrant = false;
        private Icon mDeviceIcon = null;

        public Builder() {}
@@ -493,6 +517,27 @@ public final class AssociationRequest implements Parcelable {
            return this;
        }

        /**
         * Do not attempt to grant the role corresponding to the device profile.
         *
         * <p>This will skip the permission checks and consent dialog but will not fail if the
         * role cannot be granted.</p>
         *
         * <p>Requires that the device not to have secure lock screen and that there no locked SIM
         * card. See {@link KeyguardManager#isKeyguardSecure()}</p>
         *
         * @hide
         */
        @RequiresPermission(ASSOCIATE_COMPANION_DEVICES)
        @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
        @TestApi
        @NonNull
        public Builder setSkipRoleGrant(boolean skipRoleGrant) {
            checkNotUsed();
            mSkipRoleGrant = skipRoleGrant;
            return this;
        }

        /**
         * Set the device icon for the self-managed device and to display the icon in the
         * self-managed association dialog.
@@ -521,7 +566,8 @@ public final class AssociationRequest implements Parcelable {
                        + "provide the display name of the device");
            }
            return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mSkipRoleGrant,
                    mDeviceIcon);
        }
    }

@@ -597,6 +643,7 @@ public final class AssociationRequest implements Parcelable {
                + ", associatedDevice = " + mAssociatedDevice
                + ", selfManaged = " + mSelfManaged
                + ", forceConfirmation = " + mForceConfirmation
                + ", skipRoleGrant = " + mSkipRoleGrant
                + ", packageName = " + mPackageName
                + ", userId = " + mUserId
                + ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
@@ -617,6 +664,7 @@ public final class AssociationRequest implements Parcelable {
                && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
                && mSelfManaged == that.mSelfManaged
                && mForceConfirmation == that.mForceConfirmation
                && mSkipRoleGrant == that.mSkipRoleGrant
                && Objects.equals(mPackageName, that.mPackageName)
                && mUserId == that.mUserId
                && Objects.equals(mDeviceProfilePrivilegesDescription,
@@ -637,6 +685,7 @@ public final class AssociationRequest implements Parcelable {
        _hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
        _hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
        _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
        _hash = 31 * _hash + Boolean.hashCode(mSkipRoleGrant);
        _hash = 31 * _hash + Objects.hashCode(mPackageName);
        _hash = 31 * _hash + mUserId;
        _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
@@ -659,6 +708,7 @@ public final class AssociationRequest implements Parcelable {
        if (mAssociatedDevice != null) flg |= 0x40;
        if (mPackageName != null) flg |= 0x80;
        if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
        if (mSkipRoleGrant) flg |= 0x200;

        dest.writeInt(flg);
        dest.writeParcelableList(mDeviceFilters, flags);
@@ -692,6 +742,7 @@ public final class AssociationRequest implements Parcelable {
        boolean selfManaged = (flg & 0x2) != 0;
        boolean forceConfirmation = (flg & 0x4) != 0;
        boolean skipPrompt = (flg & 0x8) != 0;
        boolean skipRoleGrant = (flg & 0x200) != 0;
        List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
                (Class<android.companion.DeviceFilter<?>>) (Class<?>)
@@ -714,6 +765,7 @@ public final class AssociationRequest implements Parcelable {
        this.mAssociatedDevice = associatedDevice;
        this.mSelfManaged = selfManaged;
        this.mForceConfirmation = forceConfirmation;
        this.mSkipRoleGrant = skipRoleGrant;
        this.mPackageName = packageName;
        this.mUserId = userId;
        com.android.internal.util.AnnotationValidations.validate(
+26 −3
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ecm.EnhancedConfirmationManager;
@@ -302,9 +303,18 @@ public class CompanionDeviceManagerService extends SystemService {
            enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
                    "create associations");

            if (request.isSkipRoleGrant()) {
                checkCallerCanSkipRoleGrant();
                mAssociationRequestsProcessor.createAssociation(userId, packageName,
                        /* macAddress= */ null, request.getDisplayName(),
                        request.getDeviceProfile(), /* associatedDevice= */ null,
                        request.isSelfManaged(), callback, /* resultReceiver= */ null,
                        request.getDeviceIcon(), /* skipRoleGrant= */ true);
            } else {
                mAssociationRequestsProcessor.processNewAssociationRequest(
                        request, packageName, userId, callback);
            }
        }

        @Override
        public PendingIntent buildAssociationCancellationIntent(String packageName,
@@ -669,7 +679,7 @@ public class CompanionDeviceManagerService extends SystemService {

            final MacAddress macAddressObj = MacAddress.fromString(macAddress);
            mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
                    null, null, null, false, null, null, null);
                    null, null, null, false, null, null, null, false);
        }

        private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -684,6 +694,19 @@ public class CompanionDeviceManagerService extends SystemService {
                    "App must have an association before calling this API");
        }

        private void checkCallerCanSkipRoleGrant() {
            final KeyguardManager keyguardManager =
                    getContext().getSystemService(KeyguardManager.class);
            if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {
                throw new SecurityException("Skipping CDM role grant requires insecure keyguard.");
            }
            if (getContext().checkCallingPermission(ASSOCIATE_COMPANION_DEVICES)
                    != PERMISSION_GRANTED) {
                throw new SecurityException(
                        "Skipping CDM role grant requires ASSOCIATE_COMPANION_DEVICES permission.");
            }
        }

        @Override
        public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
            final AssociationInfo association =
+3 −2
Original line number Diff line number Diff line
@@ -106,8 +106,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
                    boolean selfManaged = getNextBooleanArg();
                    final MacAddress macAddress = MacAddress.fromString(address);
                    mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
                            deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
                            /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
                            deviceProfile, deviceProfile, /* associatedDevice= */ null, selfManaged,
                            /* callback= */ null, /* resultReceiver= */ null,
                            /* deviceIcon= */ null, /* skipRoleGrant= */ false);
                }
                break;

+15 −5
Original line number Diff line number Diff line
@@ -282,8 +282,8 @@ public class AssociationRequestsProcessor {
        Binder.withCleanCallingIdentity(() -> {
            createAssociation(userId, packageName, macAddress, request.getDisplayName(),
                    request.getDeviceProfile(), request.getAssociatedDevice(),
                    request.isSelfManaged(),
                    callback, resultReceiver, request.getDeviceIcon());
                    request.isSelfManaged(), callback, resultReceiver, request.getDeviceIcon(),
                    /* skipRoleGrant= */ false);
        });
    }

@@ -294,7 +294,8 @@ public class AssociationRequestsProcessor {
            @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
            @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
            boolean selfManaged, @Nullable IAssociationRequestCallback callback,
            @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
            @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon,
            boolean skipRoleGrant) {
        final int id = mAssociationStore.getNextId();
        final long timestamp = System.currentTimeMillis();

@@ -303,9 +304,18 @@ public class AssociationRequestsProcessor {
                selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
                /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
                deviceIcon, /* deviceId */ null);

        if (skipRoleGrant) {
            Slog.i(TAG, "Created association for " + association.getDeviceProfile() + " and userId="
                    + association.getUserId() + ", packageName="
                    + association.getPackageName() + " without granting role");
            mAssociationStore.addAssociation(association);
            sendCallbackAndFinish(association, callback, resultReceiver);
        } else {
            // Add role holder for association (if specified) and add new association to store.
            maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
        }
    }

    /**
     * Grant a role if specified and add an association to store.