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

Commit 17236171 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

Give CDM Associations real IDs

Generate unique IDs for Association records. Whenever a new ID is update
and persist first available ID to the disk.

Bug: 197933995
Test: make, flash
Test: atest android.os.cts.CompanionDeviceManagerTest
Change-Id: I127d219d08e4b8f1e8e0e6b434b063512a502565
parent e37e45a8
Loading
Loading
Loading
Loading
+6 −2
Original line number Original line Diff line number Diff line
@@ -67,8 +67,7 @@ public final class Association implements Parcelable {
            @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
            @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
            boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
            boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
        if (associationId <= 0) {
        if (associationId <= 0) {
            // TODO: uncomment when implementing real IDs
            throw new IllegalArgumentException("Association ID should be greater than 0");
            // throw new IllegalArgumentException("Association ID should be greater than 0");
        }
        }
        validateDeviceIds(deviceIds);
        validateDeviceIds(deviceIds);


@@ -157,6 +156,11 @@ public final class Association implements Parcelable {
        return mTimeApprovedMs;
        return mTimeApprovedMs;
    }
    }


    /** @hide */
    public boolean belongsToPackage(@UserIdInt int userId, String packageName) {
        return mUserId == userId && Objects.equals(mPackageName, packageName);
    }

    @Override
    @Override
    public String toString() {
    public String toString() {
        return "Association{"
        return "Association{"
+116 −24
Original line number Original line Diff line number Diff line
@@ -38,7 +38,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.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;


import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNull;
@@ -108,6 +108,7 @@ import android.util.Log;
import android.util.PackageUtils;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsService;
@@ -118,6 +119,7 @@ import com.android.internal.infra.ServiceConnector;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.FgThread;
@@ -134,6 +136,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Date;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
@@ -141,6 +144,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.TimeZone;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Function;
import java.util.function.Predicate;


/** @hide */
/** @hide */
@SuppressLint("LongLogTag")
@SuppressLint("LongLogTag")
@@ -158,6 +162,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
        DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
    }
    }


    /** Range of Association IDs allocated for a user.*/
    static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;

    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
            ".CompanionDeviceDiscoveryService");
            ".CompanionDeviceDiscoveryService");
@@ -211,9 +218,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private final Handler mMainHandler = Handler.getMain();
    private final Handler mMainHandler = Handler.getMain();
    private CompanionDevicePresenceController mCompanionDevicePresenceController;
    private CompanionDevicePresenceController mCompanionDevicePresenceController;


    /** userId -> [association] */
    /** Maps a {@link UserIdInt} to a set of associations for the user. */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private @Nullable SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
    private final SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>();
    /**
     * A structure that consist of two nested maps, and effectively maps (userId + packageName) to
     * a list of IDs that have been previously assigned to associations for that package.
     * We maintain this structure so that we never re-use association IDs for the same package
     * (until it's uninstalled).
     */
    @GuardedBy("mLock")
    private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();


    ActivityTaskManagerInternal mAtmInternal;
    ActivityTaskManagerInternal mAtmInternal;
    ActivityManagerInternal mAmInternal;
    ActivityManagerInternal mAmInternal;
@@ -258,8 +273,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
                        + ", uid = " + uid + ")");
                        + ", uid = " + uid + ")");
                int userId = getChangingUserId();
                int userId = getChangingUserId();
                updateAssociations(
                updateAssociations(
                        as -> filter(as,
                        set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
                                a -> !Objects.equals(a.getPackageName(), packageName)),
                        userId);
                        userId);


                mCompanionDevicePresenceController.unbindDevicePresenceListener(
                mCompanionDevicePresenceController.unbindDevicePresenceListener(
@@ -757,10 +771,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind


    private void createAssociationInternal(
    private void createAssociationInternal(
            int userId, String deviceMacAddress, String packageName, String deviceProfile) {
            int userId, String deviceMacAddress, String packageName, String deviceProfile) {
        // TODO: general a valid unique ID.
        final int associationId = 0;
        final Association association = new Association(
        final Association association = new Association(
                associationId,
                getNewAssociationIdForPackage(userId, packageName),
                userId,
                userId,
                packageName,
                packageName,
                Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)),
                Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)),
@@ -773,19 +785,71 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        recordAssociation(association, userId);
        recordAssociation(association, userId);
    }
    }


    void removeAssociation(int userId, String pkg, String deviceMacAddress) {
    @GuardedBy("mLock")
        updateAssociations(associations -> filter(associations, association -> {
    @NonNull
            boolean notMatch = association.getUserId() != userId
    private Set<Integer> getPreviouslyUsedIdsForPackageLocked(
                    || !Objects.equals(association.getDeviceMacAddress(), deviceMacAddress)
            @UserIdInt int userId, @NonNull String packageName) {
                    || !Objects.equals(association.getPackageName(), pkg);
        final Set<Integer> previouslyUsedIds = mPreviouslyUsedIds.get(userId).get(packageName);
            if (!notMatch) {
        if (previouslyUsedIds != null) return previouslyUsedIds;
                onAssociationPreRemove(association);
        return emptySet();
    }

    private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
        synchronized (mLock) {
            readPersistedStateForUserIfNeededLocked(userId);

            // First: collect all IDs currently in use for this user's Associations.
            final SparseBooleanArray usedIds = new SparseBooleanArray();
            for (Association it : getAllAssociations(userId)) {
                usedIds.put(it.getAssociationId(), true);
            }

            // Second: collect all IDs that have been previously used for this package (and user).
            final Set<Integer> previouslyUsedIds =
                    getPreviouslyUsedIdsForPackageLocked(userId, packageName);

            int id = getFirstAssociationIdForUser(userId);
            final int lastAvailableIdForUser = getLastAssociationIdForUser(userId);

            // Find first ID that isn't used now AND has never been used for the given package.
            while (usedIds.get(id) || previouslyUsedIds.contains(id)) {
                // Increment and try again
                id++;
                // ... but first check if the ID is valid (within the range allocated to the user).
                if (id > lastAvailableIdForUser) {
                    throw new RuntimeException("Cannot create a new Association ID for "
                            + packageName + " for user " + userId);
                }
            }

            return id;
        }
    }

    void removeAssociation(int userId, String packageName, String deviceMacAddress) {
        updateAssociations(associations -> filterOut(associations, it -> {
            final boolean match = it.belongsToPackage(userId, packageName)
                    && Objects.equals(it.getDeviceMacAddress(), deviceMacAddress);
            if (match) {
                onAssociationPreRemove(it);
                markIdAsPreviouslyUsedForPackage(it.getAssociationId(), userId, packageName);
            }
            }
            return notMatch;
            return match;
        }), userId);
        }), userId);
        restartBleScan();
        restartBleScan();
    }
    }


    private void markIdAsPreviouslyUsedForPackage(
            int associationId, @UserIdInt int userId, @NonNull String packageName) {
        synchronized (mLock) {
            // Mark as previously used.
            readPersistedStateForUserIfNeededLocked(userId);
            mPreviouslyUsedIds.get(userId)
                    .computeIfAbsent(packageName, it -> new HashSet<>())
                    .add(associationId);
        }
    }

    void onAssociationPreRemove(Association association) {
    void onAssociationPreRemove(Association association) {
        if (association.isNotifyOnDeviceNearby()) {
        if (association.isNotifyOnDeviceNearby()) {
            mCompanionDevicePresenceController.unbindDevicePresenceListener(
            mCompanionDevicePresenceController.unbindDevicePresenceListener(
@@ -989,11 +1053,12 @@ public class CompanionDeviceManagerService extends SystemService implements Bind


            mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations));
            mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations));


            BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
            BackgroundThread.getHandler().sendMessage(
                    // TODO: pass the real used IDs ("mapped" per package) instead of emptyMap()
                    PooledLambda.obtainMessage(
                    (associations) -> mPersistentDataStore.persistStateForUser(
                            (associations, usedIds) ->
                            userId, associations, emptyMap()),
                                    mPersistentDataStore
                    updatedAssociations));
                                            .persistStateForUser(userId, associations, usedIds),
                            updatedAssociations, deepCopy(mPreviouslyUsedIds.get(userId))));


            updateAtm(userId, updatedAssociations);
            updateAtm(userId, updatedAssociations);
        }
        }
@@ -1030,12 +1095,19 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
    private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
    private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
        if (mCachedAssociations.get(userId) != null) return;
        if (mCachedAssociations.get(userId) != null) return;


        Slog.i(LOG_TAG, "Reading state for user " + userId + "  from the disk");

        final Set<Association> associations = new ArraySet<>();
        final Set<Association> associations = new ArraySet<>();
        // TODO: pass real packageName-to-usedIds map
        final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
        mPersistentDataStore.readStateForUser(userId, associations, emptyMap());
        mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds);
        Slog.i(LOG_TAG, "Read associations from disk: " + associations);

        if (DEBUG) {
            Slog.d(LOG_TAG, "  > associations=" + associations + "\n"
                    + "  > previouslyUsedIds=" + previouslyUsedIds);
        }


        mCachedAssociations.put(userId, unmodifiableSet(associations));
        mCachedAssociations.put(userId, unmodifiableSet(associations));
        mPreviouslyUsedIds.append(userId, previouslyUsedIds);
    }
    }


    private List<UserInfo> getAllUsers() {
    private List<UserInfo> getAllUsers() {
@@ -1390,6 +1462,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
        return result;
        return result;
    }
    }


    static int getFirstAssociationIdForUser(@UserIdInt int userId) {
        // We want the IDs to start from 1, not 0.
        return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
    }

    static int getLastAssociationIdForUser(@UserIdInt int userId) {
        return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
    }

    private class ShellCmd extends ShellCommand {
    private class ShellCmd extends ShellCommand {
        public static final String USAGE = "help\n"
        public static final String USAGE = "help\n"
                + "list USER_ID\n"
                + "list USER_ID\n"
@@ -1473,4 +1554,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
            CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
        }
        }
    }
    }

    private static @NonNull <T> Set<T> filterOut(
            @NonNull Set<T> set, @NonNull Predicate<? super T> predicate) {
        return CollectionUtils.filter(set, predicate.negate());
    }

    private Map<String, Set<Integer>> deepCopy(Map<String, Set<Integer>> orig) {
        final Map<String, Set<Integer>> copy = new HashMap<>(orig.size(), 1f);
        forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value)));
        return copy;
    }
}
}
+54 −8
Original line number Original line Diff line number Diff line
@@ -55,6 +55,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import java.util.Set;
@@ -151,12 +152,17 @@ final class PersistentDataStore {


    private static final String XML_TAG_STATE = "state";
    private static final String XML_TAG_STATE = "state";
    private static final String XML_TAG_ASSOCIATIONS = "associations";
    private static final String XML_TAG_ASSOCIATIONS = "associations";
    private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
    private static final String XML_TAG_ASSOCIATION = "association";
    private static final String XML_TAG_ASSOCIATION = "association";
    private static final String XML_TAG_DEVICE_ID = "device-id";
    private static final String XML_TAG_DEVICE_ID = "device-id";
    private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
    private static final String XML_TAG_PACKAGE = "package";
    private static final String XML_TAG_ID = "id";


    private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
    private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
    private static final String XML_ATTR_ID = "id";
    private static final String XML_ATTR_ID = "id";
    // Used in <package> elements, nested within <previously-used-ids> elements.
    private static final String XML_ATTR_PACKAGE_NAME = "package_name";
    // Used in <association> elements, nested within <associations> elements.
    private static final String XML_ATTR_PACKAGE = "package";
    private static final String XML_ATTR_PACKAGE = "package";
    private static final String XML_ATTR_DEVICE = "device";
    private static final String XML_ATTR_DEVICE = "device";
    private static final String XML_ATTR_PROFILE = "profile";
    private static final String XML_ATTR_PROFILE = "profile";
@@ -326,14 +332,19 @@ final class PersistentDataStore {
            throws XmlPullParserException, IOException {
            throws XmlPullParserException, IOException {
        requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
        requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);


        int associationId;
        // Before Android T Associations didn't have IDs, so when we are upgrading from S (reading
        // from V0) we need to generate and assign IDs to the existing Associations.
        // It's safe to do it here, because CDM cannot create new Associations before it reads
        // existing ones from the backup files. And the fact that we are reading from a V0 file,
        // means that CDM hasn't assigned any IDs yet, so we can just start from the first available
        // id for each user (eg. 1 for user 0; 100 001 - for user 1; 200 001 - for user 2; etc).
        int associationId = CompanionDeviceManagerService.getFirstAssociationIdForUser(userId);
        while (true) {
        while (true) {
            parser.nextTag();
            parser.nextTag();
            if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
            if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
            if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
            if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;


            associationId = 0;
            readAssociationV0(parser, userId, associationId++, out);
            readAssociationV0(parser, userId, associationId, out);
        }
        }
    }
    }


@@ -400,9 +411,29 @@ final class PersistentDataStore {
    }
    }


    private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
    private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
            @NonNull Map<String, Set<Integer>> out) throws XmlPullParserException {
            @NonNull Map<String, Set<Integer>> out) throws XmlPullParserException, IOException {
        requireStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS);
        requireStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS);
        // TODO: implement

        while (true) {
            parser.nextTag();
            if (isEndOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) break;
            if (!isStartOfTag(parser, XML_TAG_PACKAGE)) continue;

            final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE_NAME);
            final Set<Integer> usedIds = new HashSet<>();

            while (true) {
                parser.nextTag();
                if (isEndOfTag(parser, XML_TAG_PACKAGE)) break;
                if (!isStartOfTag(parser, XML_TAG_ID)) continue;

                parser.nextToken();
                final int id = Integer.parseInt(parser.getText());
                usedIds.add(id);
            }

            out.put(packageName, usedIds);
        }
    }
    }


    private static void writeAssociations(@NonNull XmlSerializer parent,
    private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -443,8 +474,23 @@ final class PersistentDataStore {
    }
    }


    private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
    private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException {
        // TODO: implement
        final XmlSerializer serializer = parent.startTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
        for (Map.Entry<String, Set<Integer>> entry : previouslyUsedIdsPerPackage.entrySet()) {
            writePreviouslyUsedIdsForPackage(serializer, entry.getKey(), entry.getValue());
        }
        serializer.endTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
    }

    private static void writePreviouslyUsedIdsForPackage(@NonNull XmlSerializer parent,
            @NonNull String packageName, @NonNull Set<Integer> previouslyUsedIds)
            throws IOException {
        final XmlSerializer serializer = parent.startTag(null, XML_TAG_PACKAGE);
        writeStringAttribute(serializer, XML_ATTR_PACKAGE_NAME, packageName);
        forEach(previouslyUsedIds, id -> serializer.startTag(null, XML_TAG_ID)
                .text(Integer.toString(id))
                .endTag(null, XML_TAG_ID));
        serializer.endTag(null, XML_TAG_PACKAGE);
    }
    }


    private static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
    private static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)