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

Commit 1ae8c3ec authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Store CDM device profile and apply role when device is connected"

parents 5cca310a 151da5b2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -3689,7 +3689,7 @@ public final class BluetoothAdapter {
     *
     * @hide
     */
    public abstract class BluetoothConnectionCallback {
    public abstract static class BluetoothConnectionCallback {
        /**
         * Callback triggered when a bluetooth device (classic or BLE) is connected
         * @param device is the connected bluetooth device
+39 −7
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ public final class Association implements Parcelable {
    private final @UserIdInt int mUserId;
    private final @NonNull String mDeviceMacAddress;
    private final @NonNull String mPackageName;
    private final @Nullable String mDeviceProfile;
    private final boolean mKeepProfilePrivilegesWhenDeviceAway;

    /** @hide */
    public int getUserId() {
@@ -45,7 +47,7 @@ public final class Association implements Parcelable {



    // Code below generated by codegen v1.0.15.
    // Code below generated by codegen v1.0.21.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
@@ -67,7 +69,9 @@ public final class Association implements Parcelable {
    public Association(
            @UserIdInt int userId,
            @NonNull String deviceMacAddress,
            @NonNull String packageName) {
            @NonNull String packageName,
            @Nullable String deviceProfile,
            boolean keepProfilePrivilegesWhenDeviceAway) {
        this.mUserId = userId;
        com.android.internal.util.AnnotationValidations.validate(
                UserIdInt.class, null, mUserId);
@@ -77,6 +81,8 @@ public final class Association implements Parcelable {
        this.mPackageName = packageName;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPackageName);
        this.mDeviceProfile = deviceProfile;
        this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway;

        // onConstructed(); // You can define this method to get a callback
    }
@@ -91,6 +97,16 @@ public final class Association implements Parcelable {
        return mPackageName;
    }

    @DataClass.Generated.Member
    public @Nullable String getDeviceProfile() {
        return mDeviceProfile;
    }

    @DataClass.Generated.Member
    public boolean isKeepProfilePrivilegesWhenDeviceAway() {
        return mKeepProfilePrivilegesWhenDeviceAway;
    }

    @Override
    @DataClass.Generated.Member
    public String toString() {
@@ -100,7 +116,9 @@ public final class Association implements Parcelable {
        return "Association { " +
                "userId = " + mUserId + ", " +
                "deviceMacAddress = " + mDeviceMacAddress + ", " +
                "packageName = " + mPackageName +
                "packageName = " + mPackageName + ", " +
                "deviceProfile = " + mDeviceProfile + ", " +
                "keepProfilePrivilegesWhenDeviceAway = " + mKeepProfilePrivilegesWhenDeviceAway +
        " }";
    }

@@ -119,7 +137,9 @@ public final class Association implements Parcelable {
        return true
                && mUserId == that.mUserId
                && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                && Objects.equals(mPackageName, that.mPackageName);
                && Objects.equals(mPackageName, that.mPackageName)
                && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                && mKeepProfilePrivilegesWhenDeviceAway == that.mKeepProfilePrivilegesWhenDeviceAway;
    }

    @Override
@@ -132,6 +152,8 @@ public final class Association implements Parcelable {
        _hash = 31 * _hash + mUserId;
        _hash = 31 * _hash + Objects.hashCode(mDeviceMacAddress);
        _hash = 31 * _hash + Objects.hashCode(mPackageName);
        _hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
        _hash = 31 * _hash + Boolean.hashCode(mKeepProfilePrivilegesWhenDeviceAway);
        return _hash;
    }

@@ -141,9 +163,14 @@ public final class Association implements Parcelable {
        // You can override field parcelling by defining methods like:
        // void parcelFieldName(Parcel dest, int flags) { ... }

        byte flg = 0;
        if (mKeepProfilePrivilegesWhenDeviceAway) flg |= 0x10;
        if (mDeviceProfile != null) flg |= 0x8;
        dest.writeByte(flg);
        dest.writeInt(mUserId);
        dest.writeString(mDeviceMacAddress);
        dest.writeString(mPackageName);
        if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
    }

    @Override
@@ -157,9 +184,12 @@ public final class Association implements Parcelable {
        // You can override field unparcelling by defining methods like:
        // static FieldType unparcelFieldName(Parcel in) { ... }

        byte flg = in.readByte();
        boolean keepProfilePrivilegesWhenDeviceAway = (flg & 0x10) != 0;
        int userId = in.readInt();
        String deviceMacAddress = in.readString();
        String packageName = in.readString();
        String deviceProfile = (flg & 0x8) == 0 ? null : in.readString();

        this.mUserId = userId;
        com.android.internal.util.AnnotationValidations.validate(
@@ -170,6 +200,8 @@ public final class Association implements Parcelable {
        this.mPackageName = packageName;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPackageName);
        this.mDeviceProfile = deviceProfile;
        this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway;

        // onConstructed(); // You can define this method to get a callback
    }
@@ -189,10 +221,10 @@ public final class Association implements Parcelable {
    };

    @DataClass.Generated(
            time = 1599083149942L,
            codegenVersion = "1.0.15",
            time = 1606940835778L,
            codegenVersion = "1.0.21",
            sourceFile = "frameworks/base/core/java/android/companion/Association.java",
            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic  int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final  boolean mKeepProfilePrivilegesWhenDeviceAway\npublic  int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
    @Deprecated
    private void __metadata() {}

+2 −1
Original line number Diff line number Diff line
@@ -321,7 +321,8 @@ public class DeviceDiscoveryService extends Service {
    }

    void onDeviceSelected(String callingPackage, String deviceAddress) {
        mServiceCallback.complete(new Association(getUserId(), deviceAddress, callingPackage));
        mServiceCallback.complete(new Association(
                getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false));
    }

    void onCancel() {
+178 −36
Original line number Diff line number Diff line
@@ -31,8 +31,12 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -47,6 +51,7 @@ import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
@@ -62,6 +67,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.SettingsStringUtil.ComponentNameSet;
import android.text.BidiFormatter;
@@ -70,6 +76,7 @@ import android.util.AtomicFile;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;

import com.android.internal.annotations.GuardedBy;
@@ -116,12 +123,16 @@ import java.util.function.Function;
//TODO on associate called again after configuration change -> replace old callback with new
//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
/** @hide */
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {

    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
            ".DeviceDiscoveryService");

    // 10 min
    public static final int DEVICE_DISCONNECT_PROFILE_REVOKE_DELAY_MS = 10 * 60 * 1000;

    private static final boolean DEBUG = false;
    private static final String LOG_TAG = "CompanionDeviceManagerService";

@@ -132,6 +143,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private static final String XML_TAG_ASSOCIATION = "association";
    private static final String XML_ATTR_PACKAGE = "package";
    private static final String XML_ATTR_DEVICE = "device";
    private static final String XML_ATTR_PROFILE = "profile";
    private static final String XML_ATTR_PERSISTENT_PROFILE_GRANTS = "persistent_profile_grants";
    private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";

    private final CompanionDeviceManagerImpl mImpl;
@@ -139,21 +152,29 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private PowerWhitelistManager mPowerWhitelistManager;
    private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
    private IAppOpsService mAppOpsManager;
    private RoleManager mRoleManager;
    private BluetoothAdapter mBluetoothAdapter;

    private IFindDeviceCallback mFindDeviceCallback;
    private AssociationRequest mRequest;
    private String mCallingPackage;
    private AndroidFuture<Association> mOngoingDeviceDiscovery;

    private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener =
            new BluetoothDeviceConnectedListener();
    private List<String> mCurrentlyConnectedDevices = new ArrayList<>();

    private final Object mLock = new Object();

    /** userId -> [association] */
    @GuardedBy("mLock")
    private @Nullable Set<Association> mCachedAssociations = null;
    private @Nullable SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();

    public CompanionDeviceManagerService(Context context) {
        super(context);
        mImpl = new CompanionDeviceManagerImpl();
        mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
        mRoleManager = context.getSystemService(RoleManager.class);
        mAppOpsManager = IAppOpsService.Stub.asInterface(
                ServiceManager.getService(Context.APP_OPS_SERVICE));

@@ -184,9 +205,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            @Override
            public void onPackageModified(String packageName) {
                int userId = getChangingUserId();
                if (!ArrayUtils.isEmpty(getAllAssociations(userId, packageName))) {
                    updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
                }
                forEach(getAllAssociations(userId, packageName), association -> {
                    updateSpecialAccessPermissionForAssociatedPackage(association);
                });
            }

        }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
@@ -197,6 +218,18 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            if (mBluetoothAdapter != null) {
                mBluetoothAdapter.registerBluetoothConnectionCallback(
                        getContext().getMainExecutor(),
                        mBluetoothDeviceConnectedListener);
            }
        }
    }

    @Override
    public void onUserUnlocking(@NonNull TargetUser user) {
        int userHandle = user.getUserIdentifier();
@@ -495,7 +528,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind

            fout.append("Companion Device Associations:").append('\n');
            synchronized (mLock) {
                forEach(mCachedAssociations, a -> {
                for (UserInfo user : getAllUsers()) {
                    forEach(mCachedAssociations.get(user.id), a -> {
                        fout.append("  ")
                                .append("u").append("" + a.getUserId()).append(": ")
                                .append(a.getPackageName()).append(" - ")
@@ -504,6 +538,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                }
            }
        }
    }

    private static int getCallingUserId() {
        return UserHandle.getUserId(Binder.getCallingUid());
@@ -513,32 +548,34 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        return Binder.getCallingUid() == Process.SYSTEM_UID;
    }

    void addAssociation(int userId, String packageName, String deviceAddress) {
        addAssociation(new Association(userId, deviceAddress, packageName));
    }

    void addAssociation(Association association) {
        updateSpecialAccessPermissionForAssociatedPackage(
                association.getPackageName(), association.getUserId());
        updateSpecialAccessPermissionForAssociatedPackage(association);
        recordAssociation(association);
    }

    void removeAssociation(int userId, String pkg, String deviceMacAddress) {
        updateAssociations(associations -> CollectionUtils.remove(associations,
                new Association(userId, deviceMacAddress, pkg)));
        updateAssociations(associations -> CollectionUtils.filter(associations, association -> {
            return association.getUserId() != userId
                    || !Objects.equals(association.getDeviceMacAddress(), deviceMacAddress)
                    || !Objects.equals(association.getPackageName(), pkg);
        }));
    }

    private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) {
        PackageInfo packageInfo = getPackageInfo(packageName, userId);
    private void updateSpecialAccessPermissionForAssociatedPackage(Association association) {
        PackageInfo packageInfo = getPackageInfo(
                association.getPackageName(),
                association.getUserId());
        if (packageInfo == null) {
            return;
        }

        Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService::
                updateSpecialAccessPermissionAsSystem, this, packageInfo).recycleOnUse());
                updateSpecialAccessPermissionAsSystem, this, association, packageInfo)
                .recycleOnUse());
    }

    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
    private void updateSpecialAccessPermissionAsSystem(
            Association association, PackageInfo packageInfo) {
        if (containsEither(packageInfo.requestedPermissions,
                android.Manifest.permission.RUN_IN_BACKGROUND,
                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -602,10 +639,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        updateAssociations(associations -> CollectionUtils.add(associations, association));
    }

    private void recordAssociation(String privilegedPackage, String deviceAddress) {
        recordAssociation(new Association(getCallingUserId(), deviceAddress, privilegedPackage));
    }

    private void updateAssociations(Function<Set<Association>, Set<Association>> update) {
        updateAssociations(update, getCallingUserId());
    }
@@ -625,7 +658,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            if (DEBUG) {
                Slog.i(LOG_TAG, "Updating associations: " + old + "  -->  " + associations);
            }
            mCachedAssociations = Collections.unmodifiableSet(associations);
            mCachedAssociations.put(userId, Collections.unmodifiableSet(associations));
            BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                    CompanionDeviceManagerService::persistAssociations,
                    this, associations, userId));
@@ -651,10 +684,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                    xml.startTag(null, XML_TAG_ASSOCIATIONS);

                    forEach(associations, association -> {
                        xml.startTag(null, XML_TAG_ASSOCIATION)
                        XmlSerializer tag = xml.startTag(null, XML_TAG_ASSOCIATION)
                                .attribute(null, XML_ATTR_PACKAGE, association.getPackageName())
                                .attribute(null, XML_ATTR_DEVICE, association.getDeviceMacAddress())
                                .endTag(null, XML_TAG_ASSOCIATION);
                                .attribute(null, XML_ATTR_DEVICE,
                                        association.getDeviceMacAddress());
                        if (association.getDeviceProfile() != null) {
                            tag.attribute(null, XML_ATTR_PROFILE, association.getDeviceProfile());
                            tag.attribute(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS,
                                    Boolean.toString(
                                            association.isKeepProfilePrivilegesWhenDeviceAway()));
                        }
                        tag.endTag(null, XML_TAG_ASSOCIATION);
                    });

                    xml.endTag(null, XML_TAG_ASSOCIATIONS);
@@ -678,15 +718,19 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    @Nullable
    private Set<Association> getAllAssociations(int userId) {
        synchronized (mLock) {
            if (mCachedAssociations == null) {
                mCachedAssociations = Collections.unmodifiableSet(
                        emptyIfNull(readAllAssociations(userId)));
            if (mCachedAssociations.get(userId) == null) {
                mCachedAssociations.put(userId, Collections.unmodifiableSet(
                        emptyIfNull(readAllAssociations(userId))));
                if (DEBUG) {
                    Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations);
                }
            }
            return mCachedAssociations;
            return mCachedAssociations.get(userId);
        }
    }

    private List<UserInfo> getAllUsers() {
        return getContext().getSystemService(UserManager.class).getUsers();
    }

    @Nullable
@@ -714,10 +758,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                    final String appPackage = parser.getAttributeValue(null, XML_ATTR_PACKAGE);
                    final String deviceAddress = parser.getAttributeValue(null, XML_ATTR_DEVICE);

                    final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE);
                    final boolean persistentGrants = Boolean.valueOf(
                            parser.getAttributeValue(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS));

                    if (appPackage == null || deviceAddress == null) continue;

                    result = ArrayUtils.add(result,
                            new Association(userId, deviceAddress, appPackage));
                            new Association(userId, deviceAddress, appPackage,
                                    profile, persistentGrants));
                }
                return result;
            } catch (XmlPullParserException | IOException e) {
@@ -727,6 +776,77 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        }
    }

    void onDeviceConnected(String address) {
        mCurrentlyConnectedDevices.add(address);

        Handler.getMain().removeCallbacksAndMessages(getDisconnectJobHandlerId(address));

        for (UserInfo user : getAllUsers()) {
            for (Association association : getAllAssociations(user.id)) {
                if (Objects.equals(address, association.getDeviceMacAddress())) {
                    if (association.getDeviceProfile() != null) {
                        Log.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
                                + " to " + association.getPackageName()
                                + " due to device connected: " + address);
                        mRoleManager.addRoleHolderAsUser(
                                association.getDeviceProfile(),
                                association.getPackageName(),
                                RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
                                UserHandle.of(association.getUserId()),
                                getContext().getMainExecutor(),
                                success -> {
                                    if (!success) {
                                        Log.e(LOG_TAG, "Failed to grant device profile role "
                                                + association.getDeviceProfile()
                                                + " to " + association.getPackageName()
                                                + " for user " + association.getUserId());
                                    }
                                });
                    }
                }
            }
        }
    }

    void onDeviceDisconnected(String address) {
        mCurrentlyConnectedDevices.remove(address);

        Handler.getMain().postDelayed(() -> {
            if (!mCurrentlyConnectedDevices.contains(address)) {
                for (UserInfo user : getAllUsers()) {
                    for (Association association : getAllAssociations(user.id)) {
                        if (association.getDeviceProfile() != null
                                && Objects.equals(address, association.getDeviceMacAddress())
                                && !association.isKeepProfilePrivilegesWhenDeviceAway()) {
                            Log.i(LOG_TAG, "Revoking role " + association.getDeviceProfile()
                                    + " to " + association.getPackageName()
                                    + " due to device disconnected: " + address);
                            mRoleManager.removeRoleHolderAsUser(
                                    association.getDeviceProfile(),
                                    association.getPackageName(),
                                    RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
                                    UserHandle.of(association.getUserId()),
                                    getContext().getMainExecutor(),
                                    success -> {
                                        if (!success) {
                                            Log.e(LOG_TAG, "Failed to revoke device profile role "
                                                    + association.getDeviceProfile()
                                                    + " to " + association.getPackageName()
                                                    + " for user " + association.getUserId());
                                        }
                                    });
                        }
                    }
                }
            }
        }, getDisconnectJobHandlerId(address), DEVICE_DISCONNECT_PROFILE_REVOKE_DELAY_MS);
    }

    @NonNull
    private String getDisconnectJobHandlerId(String address) {
        return "CDM_onDisconnected_" + address;
    }

    private class ShellCmd extends ShellCommand {
        public static final String USAGE = "help\n"
                + "list USER_ID\n"
@@ -749,13 +869,22 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                } break;

                case "associate": {
                    addAssociation(getNextArgInt(), getNextArgRequired(), getNextArgRequired());
                    addAssociation(new Association(getNextArgInt(), getNextArgRequired(),
                            getNextArgRequired(), null, false));
                } break;

                case "disassociate": {
                    removeAssociation(getNextArgInt(), getNextArgRequired(), getNextArgRequired());
                } break;

                case "simulate_connect": {
                    onDeviceConnected(getNextArgRequired());
                } break;

                case "simulate_disconnect": {
                    onDeviceDisconnected(getNextArgRequired());
                } break;

                default: return handleDefaultCommands(cmd);
            }
            return 0;
@@ -771,4 +900,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        }
    }


    private class BluetoothDeviceConnectedListener
            extends BluetoothAdapter.BluetoothConnectionCallback {
        @Override
        public void onDeviceConnected(BluetoothDevice device) {
            CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress());
        }

        @Override
        public void onDeviceDisconnected(BluetoothDevice device) {
            CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
        }
    }
}