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

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

Merge "Introduce CDM AssociationStore"

parents 806e4df8 10c9fa77
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -197,6 +197,20 @@ public final class AssociationInfo implements Parcelable {
        return macAddress.equals(mDeviceMacAddress);
    }

    /** @hide */
    public @NonNull String toShortString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("id=").append(mId);
        if (mDeviceMacAddress != null) {
            sb.append(", addr=").append(getDeviceMacAddressAsString());
        }
        if (mSelfManaged) {
            sb.append(", self-managed");
        }
        sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName);
        return sb.toString();
    }

    @Override
    public String toString() {
        return "Association{"
+25 −16
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
import static android.content.ComponentName.createRelative;

import static com.android.internal.util.CollectionUtils.filter;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
@@ -57,6 +56,7 @@ import com.android.internal.util.ArrayUtils;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
@@ -124,14 +124,17 @@ class AssociationRequestsProcessor {
    private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
    private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;

    private final Context mContext;
    private final CompanionDeviceManagerService mService;
    private final PackageManagerInternal mPackageManager;
    private final @NonNull Context mContext;
    private final @NonNull CompanionDeviceManagerService mService;
    private final @NonNull PackageManagerInternal mPackageManager;
    private final @NonNull AssociationStore mAssociationStore;

    AssociationRequestsProcessor(CompanionDeviceManagerService service) {
    AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
            @NonNull AssociationStore associationStore) {
        mContext = service.getContext();
        mService = service;
        mPackageManager = service.mPackageManagerInternal;
        mAssociationStore = associationStore;
    }

    /**
@@ -330,18 +333,24 @@ class AssociationRequestsProcessor {
        }

        // Throttle frequent associations
        long now = System.currentTimeMillis();
        Set<AssociationInfo> recentAssociations = filter(
                mService.getAssociations(userId, packageName),
                a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS);

        if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
            Slog.w(TAG, "Too many associations. " + packageName
                    + " already associated " + recentAssociations.size()
                    + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS
                    + "ms: " + recentAssociations);
        final long now = System.currentTimeMillis();
        final List<AssociationInfo> associationForPackage =
                mAssociationStore.getAssociationsForPackage(userId, packageName);
        // Number of "recent" associations.
        int recent = 0;
        for (AssociationInfo association : associationForPackage) {
            final boolean isRecent =
                    now - association.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS;
            if (isRecent) {
                if (++recent >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
                    Slog.w(TAG, "Too many associations: " + packageName + " already "
                            + "associated " + recent + " devices within the last "
                            + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + "ms");
                    return false;
                }
            }
        }

        String[] sameOemCerts = mContext.getResources()
                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);

+126 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;

/**
 * Interface for a store of {@link AssociationInfo}-s.
 */
public interface AssociationStore {

    @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
            CHANGE_TYPE_ADDED,
            CHANGE_TYPE_REMOVED,
            CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
            CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ChangeType {}

    int CHANGE_TYPE_ADDED = 0;
    int CHANGE_TYPE_REMOVED = 1;
    int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
    int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;

    /**  Listener for any changes to {@link AssociationInfo}-s. */
    interface OnChangeListener {
        default void onAssociationChanged(
                @ChangeType int changeType, AssociationInfo association) {}

        default void onAssociationAdded(AssociationInfo association) {}

        default void onAssociationRemoved(AssociationInfo association) {}

        default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
    }

    /**
     * @return all CDM associations.
     */
    @NonNull
    Collection<AssociationInfo> getAssociations();

    /**
     * @return a {@link List} of associations that belong to the user.
     */
    @NonNull
    List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);

    /**
     * @return a {@link List} of association that belong to the package.
     */
    @NonNull
    List<AssociationInfo> getAssociationsForPackage(
            @UserIdInt int userId, @NonNull String packageName);

    /**
     * @return an association with the given address that belong to the given package if such an
     * association exists, otherwise {@code null}.
     */
    @Nullable
    AssociationInfo getAssociationsForPackageWithAddress(
            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);

    /**
     * @return an association with the given id if such an association exists, otherwise
     * {@code null}.
     */
    @Nullable
    AssociationInfo getAssociationById(int id);

    /**
     * @return all associations with the given MAc address.
     */
    @NonNull
    List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);

    /** Register a {@link OnChangeListener} */
    void registerListener(@NonNull OnChangeListener listener);

    /** Un-register a previously registered {@link OnChangeListener} */
    void unregisterListener(@NonNull OnChangeListener listener);

    /** @hide */
    static String changeTypeToString(@ChangeType int changeType) {
        switch (changeType) {
            case CHANGE_TYPE_ADDED:
                return "ASSOCIATION_ADDED";

            case CHANGE_TYPE_REMOVED:
                return "ASSOCIATION_REMOVED";

            case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
                return "ASSOCIATION_UPDATED";

            case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
                return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";

            default:
                return "Unknown (" + changeType + ")";
        }
    }
}
+302 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
 * <ul>
 * <li> {@link #addAssociation(AssociationInfo)}
 * <li> {@link #removeAssociation(int)}
 * <li> {@link #updateAssociation(AssociationInfo)}
 * </ul>
 *
 * The class has package-private access level, and instances of the class should only be created by
 * the {@link CompanionDeviceManagerService}.
 * Other system component (both inside and outside if the com.android.server.companion package)
 * should use public {@link AssociationStore} interface.
 */
class AssociationStoreImpl implements AssociationStore {
    private static final boolean DEBUG = false;
    private static final String TAG = "AssociationStore";

    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final Map<Integer, AssociationInfo> mIdMap;
    @GuardedBy("mLock")
    private final Map<MacAddress, Set<Integer>> mAddressMap;
    @GuardedBy("mLock")
    private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>();

    @GuardedBy("mListeners")
    private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();

    AssociationStoreImpl(Collection<AssociationInfo> associations) {
        synchronized (mLock) {
            final int size = associations.size();
            mIdMap = new HashMap<>(size);
            mAddressMap = new HashMap<>(size);

            for (AssociationInfo association : associations) {
                final int id = association.getId();
                mIdMap.put(id, association);

                final MacAddress address = association.getDeviceMacAddress();
                if (address != null) {
                    mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
                }
            }
        }
    }

    void addAssociation(@NonNull AssociationInfo association) {
        final int id = association.getId();

        if (DEBUG) {
            Log.i(TAG, "addAssociation() " + association.toShortString());
            Log.d(TAG, "  association=" + association);
        }

        synchronized (mLock) {
            if (mIdMap.containsKey(id)) {
                if (DEBUG) Log.w(TAG, "Association already stored.");
                return;
            }
            mIdMap.put(id, association);

            final MacAddress address = association.getDeviceMacAddress();
            if (address != null) {
                mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
            }

            invalidateCacheForUserLocked(association.getUserId());
        }

        broadcastChange(CHANGE_TYPE_ADDED, association);
    }

    void updateAssociation(@NonNull AssociationInfo updated) {
        final int id = updated.getId();

        if (DEBUG) {
            Log.i(TAG, "updateAssociation() " + updated.toShortString());
            Log.d(TAG, "  updated=" + updated);
        }

        final AssociationInfo current;
        final boolean macAddressChanged;
        synchronized (mLock) {
            current = mIdMap.get(id);
            if (current == null) {
                if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
                return;
            }
            if (DEBUG) Log.d(TAG, "  current=" + current);

            if (current.equals(updated)) {
                if (DEBUG) Log.w(TAG, "  No changes.");
                return;
            }

            // Update the ID-to-Association map.
            mIdMap.put(id, updated);

            // Update the MacAddress-to-List<Association> map if needed.
            final MacAddress updatedAddress = updated.getDeviceMacAddress();
            final MacAddress currentAddress = current.getDeviceMacAddress();
            macAddressChanged = Objects.equals(
                    current.getDeviceMacAddress(), updated.getDeviceMacAddress());
            if (macAddressChanged) {
                if (currentAddress != null) {
                    mAddressMap.get(currentAddress).remove(id);
                }
                if (updatedAddress != null) {
                    mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
                }
            }
        }

        final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
                : CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
        broadcastChange(changeType, updated);
    }

    void removeAssociation(int id) {
        if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);

        final AssociationInfo association;
        synchronized (mLock) {
            association = mIdMap.remove(id);

            if (association == null) {
                if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
                return;
            } else {
                if (DEBUG) {
                    Log.i(TAG, "removed " + association.toShortString());
                    Log.d(TAG, "  association=" + association);
                }
            }

            final MacAddress macAddress = association.getDeviceMacAddress();
            if (macAddress != null) {
                mAddressMap.get(macAddress).remove(id);
            }

            invalidateCacheForUserLocked(association.getUserId());
        }

        broadcastChange(CHANGE_TYPE_REMOVED, association);
    }

    public @NonNull Collection<AssociationInfo> getAssociations() {
        final Collection<AssociationInfo> allAssociations;
        synchronized (mLock) {
            allAssociations = mIdMap.values();
        }
        return Collections.unmodifiableCollection(allAssociations);
    }

    public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
        synchronized (mLock) {
            return getAssociationsForUserLocked(userId);
        }
    }

    public @NonNull List<AssociationInfo> getAssociationsForPackage(
            @UserIdInt int userId, @NonNull String packageName) {
        final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
        final List<AssociationInfo> associationsForPackage =
                CollectionUtils.filter(associationsForUser,
                        it -> it.getPackageName().equals(packageName));
        return Collections.unmodifiableList(associationsForPackage);
    }

    public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
            @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
        final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
        return CollectionUtils.find(associations,
                it -> it.belongsToPackage(userId, packageName));
    }

    public @Nullable AssociationInfo getAssociationById(int id) {
        synchronized (mLock) {
            return mIdMap.get(id);
        }
    }

    public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
        final MacAddress address = MacAddress.fromString(macAddress);

        synchronized (mLock) {
            final Set<Integer> ids = mAddressMap.get(address);
            if (ids == null) return Collections.emptyList();

            final List<AssociationInfo> associations = new ArrayList<>();
            for (AssociationInfo association : mIdMap.values()) {
                if (address.equals(association.getDeviceMacAddress())) {
                    associations.add(association);
                }
            }

            return Collections.unmodifiableList(associations);
        }
    }

    @GuardedBy("mLock")
    private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
        final List<AssociationInfo> cached = mCachedPerUser.get(userId);
        if (cached != null) {
            return cached;
        }

        final List<AssociationInfo> associationsForUser = new ArrayList<>();
        for (AssociationInfo association : mIdMap.values()) {
            if (association.getUserId() == userId) {
                associationsForUser.add(association);
            }
        }
        final List<AssociationInfo> set = Collections.unmodifiableList(associationsForUser);
        mCachedPerUser.set(userId, set);
        return set;
    }

    @GuardedBy("mLock")
    private void invalidateCacheForUserLocked(@UserIdInt int userId) {
        mCachedPerUser.delete(userId);
    }

    public void registerListener(@NonNull OnChangeListener listener) {
        synchronized (mListeners) {
            mListeners.add(listener);
        }
    }

    public void unregisterListener(@NonNull OnChangeListener listener) {
        synchronized (mListeners) {
            mListeners.remove(listener);
        }
    }

    private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
        synchronized (mListeners) {
            for (OnChangeListener listener : mListeners) {
                listener.onAssociationChanged(changeType, association);

                switch (changeType) {
                    case CHANGE_TYPE_ADDED:
                        listener.onAssociationAdded(association);
                        break;

                    case CHANGE_TYPE_REMOVED:
                        listener.onAssociationRemoved(association);
                        break;

                    case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
                        listener.onAssociationUpdated(association, true);
                        break;

                    case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
                        listener.onAssociationUpdated(association, false);
                        break;
                }
            }
        }
    }
}
+225 −284

File changed.

Preview size limit exceeded, changes collapsed.

Loading