Introduce CDM AssociationStore
Add public (within system_server) AssociationStore interface that provides APIs for retreiving and monitoring changes to the CDM associations. Add AssociationStoreImpl - implementation of the AssociationStore which additionally adds adds methods for adding, removing and updating associations to be used by CdmService. Bug: 211398735 Test: atest CtsCompanionDeviceManagerCoreTestCases Test: atest CtsCompanionDeviceManagerUiAutomationTestCases Change-Id: I82bc948c9949c3034b539530ef9641baf220601c
This commit is contained in:
@@ -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{"
|
||||
|
||||
@@ -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);
|
||||
return false;
|
||||
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);
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,12 @@ import static android.os.Binder.getCallingUid;
|
||||
import static android.os.Process.SYSTEM_UID;
|
||||
import static android.os.UserHandle.getCallingUserId;
|
||||
|
||||
import static com.android.internal.util.CollectionUtils.add;
|
||||
import static com.android.internal.util.CollectionUtils.any;
|
||||
import static com.android.internal.util.CollectionUtils.filter;
|
||||
import static com.android.internal.util.CollectionUtils.find;
|
||||
import static com.android.internal.util.CollectionUtils.forEach;
|
||||
import static com.android.internal.util.CollectionUtils.map;
|
||||
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.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
|
||||
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
|
||||
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
|
||||
import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
|
||||
@@ -45,8 +42,6 @@ import static com.android.server.companion.PermissionsUtils.enforceCallerIsSyste
|
||||
import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
|
||||
import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
@@ -82,7 +77,6 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.MacAddress;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.os.Binder;
|
||||
@@ -112,9 +106,7 @@ import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
@@ -127,6 +119,7 @@ import java.io.PrintWriter;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -135,12 +128,11 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** @hide */
|
||||
@SuppressLint("LongLogTag")
|
||||
public class CompanionDeviceManagerService extends SystemService {
|
||||
public class CompanionDeviceManagerService extends SystemService
|
||||
implements AssociationStore.OnChangeListener {
|
||||
static final String LOG_TAG = "CompanionDeviceManagerService";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
@@ -162,10 +154,11 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
sDateFormat.setTimeZone(TimeZone.getDefault());
|
||||
}
|
||||
|
||||
private final CompanionDeviceManagerImpl mImpl;
|
||||
// Persistent data store for all Associations.
|
||||
private final PersistentDataStore mPersistentDataStore;
|
||||
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
|
||||
private PersistentDataStore mPersistentStore;
|
||||
private AssociationStoreImpl mAssociationStore;
|
||||
private AssociationRequestsProcessor mAssociationRequestsProcessor;
|
||||
|
||||
private PowerWhitelistManager mPowerWhitelistManager;
|
||||
private IAppOpsService mAppOpsManager;
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
@@ -187,20 +180,16 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
final Object mLock = new Object();
|
||||
final Handler mMainHandler = Handler.getMain();
|
||||
private CompanionDevicePresenceController mCompanionDevicePresenceController;
|
||||
|
||||
/** Maps a {@link UserIdInt} to a set of associations for the user. */
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<Set<AssociationInfo>> 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")
|
||||
@GuardedBy("mPreviouslyUsedIds")
|
||||
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
|
||||
|
||||
ActivityTaskManagerInternal mAtmInternal;
|
||||
@@ -209,8 +198,6 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
|
||||
public CompanionDeviceManagerService(Context context) {
|
||||
super(context);
|
||||
mImpl = new CompanionDeviceManagerImpl();
|
||||
mPersistentDataStore = new PersistentDataStore();
|
||||
|
||||
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
|
||||
mAppOpsManager = IAppOpsService.Stub.asInterface(
|
||||
@@ -221,49 +208,36 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
mPermissionControllerManager = requireNonNull(
|
||||
context.getSystemService(PermissionControllerManager.class));
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
|
||||
mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
|
||||
|
||||
registerPackageMonitor();
|
||||
}
|
||||
|
||||
private void registerPackageMonitor() {
|
||||
new PackageMonitor() {
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
|
||||
|
||||
clearAssociationForPackage(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageDataCleared(String packageName, int uid) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
|
||||
|
||||
clearAssociationForPackage(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageModified(String packageName) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
|
||||
|
||||
forEach(getAssociations(userId, packageName), association ->
|
||||
updateSpecialAccessPermissionForAssociatedPackage(association));
|
||||
}
|
||||
}.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
|
||||
mPersistentStore = new PersistentDataStore();
|
||||
final Set<AssociationInfo> allAssociations = new ArraySet<>();
|
||||
|
||||
synchronized (mPreviouslyUsedIds) {
|
||||
// The data is stored in DE directories, so we can read the data for all users now
|
||||
// (which would not be possible if the data was stored to CE directories).
|
||||
mPersistentStore.readStateForUsers(
|
||||
mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
|
||||
}
|
||||
|
||||
mAssociationStore = new AssociationStoreImpl(allAssociations);
|
||||
mAssociationStore.registerListener(this);
|
||||
|
||||
mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
|
||||
mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore);
|
||||
|
||||
// Publish "binder service"
|
||||
final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
|
||||
publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
|
||||
registerPackageMonitor();
|
||||
|
||||
// Init Bluetooth
|
||||
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (mBluetoothAdapter != null) {
|
||||
@@ -282,7 +256,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
@Override
|
||||
public void onUserUnlocking(@NonNull TargetUser user) {
|
||||
final int userId = user.getUserIdentifier();
|
||||
final Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
|
||||
final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId);
|
||||
|
||||
if (associations.isEmpty()) return;
|
||||
|
||||
@@ -293,42 +267,18 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
MINUTES.toMillis(10));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Set<AssociationInfo> getAllAssociationsForUser(@UserIdInt int userId) {
|
||||
synchronized (mLock) {
|
||||
readPersistedStateForUserIfNeededLocked(userId);
|
||||
// This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
|
||||
// we just called adds an empty set, if there was no previously saved data.
|
||||
return mCachedAssociations.get(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Set<AssociationInfo> getAssociations(@UserIdInt int userId, @NonNull String packageName) {
|
||||
return filter(getAllAssociationsForUser(userId),
|
||||
a -> a.belongsToPackage(userId, packageName));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AssociationInfo getAssociation(int associationId) {
|
||||
return find(getAllAssociations(), association -> association.getId() == associationId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AssociationInfo getAssociation(
|
||||
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
|
||||
return find(getAssociations(userId, packageName), a -> a.isLinkedTo(macAddress));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AssociationInfo getAssociationWithCallerChecks(
|
||||
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
|
||||
return sanitizeWithCallerChecks(getAssociation(userId, packageName, macAddress));
|
||||
final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
|
||||
userId, packageName, macAddress);
|
||||
return sanitizeWithCallerChecks(association);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AssociationInfo getAssociationWithCallerChecks(int associationId) {
|
||||
return sanitizeWithCallerChecks(getAssociation(associationId));
|
||||
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
|
||||
return sanitizeWithCallerChecks(association);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -344,19 +294,6 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
return association;
|
||||
}
|
||||
|
||||
private Set<AssociationInfo> getAllAssociations() {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final Set<AssociationInfo> result = new ArraySet<>();
|
||||
for (UserInfo user : mUserManager.getAliveUsers()) {
|
||||
result.addAll(getAllAssociationsForUser(user.id));
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
void maybeGrantAutoRevokeExemptions() {
|
||||
Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
@@ -369,10 +306,8 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
|
||||
try {
|
||||
Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
|
||||
if (associations == null) {
|
||||
continue;
|
||||
}
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsForUser(userId);
|
||||
for (AssociationInfo a : associations) {
|
||||
try {
|
||||
int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
|
||||
@@ -387,6 +322,61 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssociationChanged(
|
||||
@AssociationStore.ChangeType int changeType, AssociationInfo association) {
|
||||
final int id = association.getId();
|
||||
final int userId = association.getUserId();
|
||||
final String packageName = association.getPackageName();
|
||||
|
||||
if (changeType == AssociationStore.CHANGE_TYPE_REMOVED) {
|
||||
markIdAsPreviouslyUsedForPackage(id, userId, packageName);
|
||||
}
|
||||
|
||||
final List<AssociationInfo> updatedAssociations =
|
||||
mAssociationStore.getAssociationsForUser(userId);
|
||||
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
|
||||
BackgroundThread.getHandler().post(() ->
|
||||
mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser));
|
||||
|
||||
// Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED.
|
||||
// Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's
|
||||
// configs, which "listeners" won't (and shouldn't) be able to see.
|
||||
if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) {
|
||||
notifyListeners(userId, updatedAssociations);
|
||||
}
|
||||
updateAtm(userId, updatedAssociations);
|
||||
|
||||
restartBleScan();
|
||||
}
|
||||
|
||||
private void notifyListeners(
|
||||
@UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
|
||||
mListeners.broadcast((listener, callbackUserId) -> {
|
||||
if ((int) callbackUserId == userId) {
|
||||
try {
|
||||
listener.onAssociationsChanged(associations);
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markIdAsPreviouslyUsedForPackage(
|
||||
int associationId, @UserIdInt int userId, @NonNull String packageName) {
|
||||
synchronized (mPreviouslyUsedIds) {
|
||||
Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
|
||||
if (usedIdsForUser == null) {
|
||||
usedIdsForUser = new HashMap<>();
|
||||
mPreviouslyUsedIds.put(userId, usedIdsForUser);
|
||||
}
|
||||
|
||||
final Set<Integer> usedIdsForPackage =
|
||||
usedIdsForUser.computeIfAbsent(packageName, it -> new HashSet<>());
|
||||
usedIdsForPackage.add(associationId);
|
||||
}
|
||||
}
|
||||
|
||||
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
|
||||
|
||||
@Override
|
||||
@@ -424,8 +414,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
checkUsesFeature(packageName, getCallingUserId());
|
||||
}
|
||||
|
||||
return new ArrayList<>(
|
||||
CompanionDeviceManagerService.this.getAssociations(userId, packageName));
|
||||
return mAssociationStore.getAssociationsForPackage(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -433,8 +422,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
enforceCallerCanInteractWithUserId(getContext(), userId);
|
||||
enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
|
||||
|
||||
return new ArrayList<>(
|
||||
CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
|
||||
return mAssociationStore.getAssociationsForUser(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -470,7 +458,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
+ "(ie. it belongs to a different package or a different user).");
|
||||
}
|
||||
|
||||
disassociateInternal(userId, association.getId());
|
||||
disassociateInternal(association.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -483,7 +471,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
+ "or belongs to a different user");
|
||||
}
|
||||
|
||||
disassociateInternal(association.getUserId(), associationId);
|
||||
disassociateInternal(associationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -540,7 +528,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
return true;
|
||||
}
|
||||
|
||||
return any(CompanionDeviceManagerService.this.getAssociations(userId, packageName),
|
||||
return any(mAssociationStore.getAssociationsForPackage(userId, packageName),
|
||||
a -> a.isLinkedTo(macAddress));
|
||||
}
|
||||
|
||||
@@ -623,25 +611,18 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
final int userId = getCallingUserId();
|
||||
enforceCallerIsSystemOr(userId, packageName);
|
||||
|
||||
Set<AssociationInfo> deviceAssociations = filter(
|
||||
CompanionDeviceManagerService.this.getAssociations(userId, packageName),
|
||||
a -> a.isLinkedTo(deviceAddress));
|
||||
final AssociationInfo association =
|
||||
mAssociationStore.getAssociationsForPackageWithAddress(
|
||||
userId, packageName, deviceAddress);
|
||||
|
||||
if (deviceAssociations.isEmpty()) {
|
||||
if (association == null) {
|
||||
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
|
||||
+ " is not associated with device " + deviceAddress
|
||||
+ " for user " + userId));
|
||||
}
|
||||
|
||||
updateAssociations(associations -> map(associations, association -> {
|
||||
if (association.belongsToPackage(userId, packageName)
|
||||
&& association.isLinkedTo(deviceAddress)) {
|
||||
association.setNotifyOnDeviceNearby(active);
|
||||
}
|
||||
return association;
|
||||
}), userId);
|
||||
|
||||
restartBleScan();
|
||||
association.setNotifyOnDeviceNearby(active);
|
||||
mAssociationStore.updateAssociation(association);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -664,14 +645,16 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
enforceCallerIsSystemOr(userId, callingPackage);
|
||||
|
||||
checkState(!ArrayUtils.isEmpty(
|
||||
CompanionDeviceManagerService.this.getAssociations(userId, callingPackage)),
|
||||
mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
|
||||
"App must have an association before calling this API");
|
||||
checkUsesFeature(callingPackage, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
|
||||
final AssociationInfo association = getAssociation(userId, packageName, macAddress);
|
||||
final AssociationInfo association =
|
||||
mAssociationStore.getAssociationsForPackageWithAddress(
|
||||
userId, packageName, macAddress);
|
||||
if (association == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -684,7 +667,8 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
|
||||
throws RemoteException {
|
||||
enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
|
||||
new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
|
||||
new CompanionDeviceShellCommand(
|
||||
CompanionDeviceManagerService.this, mAssociationStore)
|
||||
.exec(this, in, out, err, args, callback, resultReceiver);
|
||||
}
|
||||
|
||||
@@ -697,13 +681,8 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
|
||||
fout.append("Companion Device Associations:").append('\n');
|
||||
synchronized (mLock) {
|
||||
for (UserInfo user : getAllUsers()) {
|
||||
forEach(mCachedAssociations.get(user.id), a -> {
|
||||
fout.append(" ").append(a.toString()).append('\n');
|
||||
});
|
||||
}
|
||||
|
||||
for (AssociationInfo a : mAssociationStore.getAssociations()) {
|
||||
fout.append(" ").append(a.toString()).append('\n');
|
||||
}
|
||||
|
||||
fout.append("Currently Connected Devices:").append('\n');
|
||||
@@ -754,31 +733,56 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
@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);
|
||||
Slog.i(LOG_TAG, "New CDM association created=" + association);
|
||||
mAssociationStore.addAssociation(association);
|
||||
|
||||
updateSpecialAccessPermissionForAssociatedPackage(association);
|
||||
recordAssociation(association, userId);
|
||||
|
||||
return association;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@NonNull
|
||||
private Map<String, Set<Integer>> getPreviouslyUsedIdsForUser(@UserIdInt int userId) {
|
||||
synchronized (mPreviouslyUsedIds) {
|
||||
return getPreviouslyUsedIdsForUserLocked(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mPreviouslyUsedIds")
|
||||
@NonNull
|
||||
private Map<String, Set<Integer>> getPreviouslyUsedIdsForUserLocked(@UserIdInt int userId) {
|
||||
final Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId);
|
||||
if (usedIdsForUser == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return deepUnmodifiableCopy(usedIdsForUser);
|
||||
}
|
||||
|
||||
@GuardedBy("mPreviouslyUsedIds")
|
||||
@NonNull
|
||||
private Set<Integer> getPreviouslyUsedIdsForPackageLocked(
|
||||
@UserIdInt int userId, @NonNull String packageName) {
|
||||
final Set<Integer> previouslyUsedIds = mPreviouslyUsedIds.get(userId).get(packageName);
|
||||
if (previouslyUsedIds != null) return previouslyUsedIds;
|
||||
return emptySet();
|
||||
// "Deeply unmodifiable" map: the map itself and the Set<Integer> values it contains are all
|
||||
// unmodifiable.
|
||||
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUserLocked(userId);
|
||||
final Set<Integer> usedIdsForPackage = usedIdsForUser.get(packageName);
|
||||
|
||||
if (usedIdsForPackage == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
//The set is already unmodifiable.
|
||||
return usedIdsForPackage;
|
||||
}
|
||||
|
||||
private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
|
||||
synchronized (mLock) {
|
||||
readPersistedStateForUserIfNeededLocked(userId);
|
||||
|
||||
synchronized (mPreviouslyUsedIds) {
|
||||
// First: collect all IDs currently in use for this user's Associations.
|
||||
final SparseBooleanArray usedIds = new SparseBooleanArray();
|
||||
for (AssociationInfo it : getAllAssociationsForUser(userId)) {
|
||||
for (AssociationInfo it : mAssociationStore.getAssociationsForUser(userId)) {
|
||||
usedIds.put(it.getId(), true);
|
||||
}
|
||||
|
||||
@@ -804,41 +808,14 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO also revoke notification access
|
||||
void disassociateInternal(@UserIdInt int userId, int associationId) {
|
||||
updateAssociations(associations ->
|
||||
filterOut(associations, it -> {
|
||||
if (it.getId() != associationId) return false;
|
||||
|
||||
onAssociationPreRemove(it);
|
||||
markIdAsPreviouslyUsedForPackage(
|
||||
it.getId(), it.getUserId(), it.getPackageName());
|
||||
return true;
|
||||
}), userId);
|
||||
|
||||
restartBleScan();
|
||||
//TODO: also revoke notification access
|
||||
void disassociateInternal(int associationId) {
|
||||
onAssociationPreRemove(associationId);
|
||||
mAssociationStore.removeAssociation(associationId);
|
||||
}
|
||||
|
||||
void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
|
||||
if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
|
||||
|
||||
mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
|
||||
updateAssociations(set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
|
||||
userId);
|
||||
}
|
||||
|
||||
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(AssociationInfo association) {
|
||||
void onAssociationPreRemove(int associationId) {
|
||||
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
|
||||
if (association.isNotifyOnDeviceNearby()
|
||||
|| (association.isSelfManaged()
|
||||
&& mPresentSelfManagedDevices.contains(association.getId()))) {
|
||||
@@ -849,7 +826,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
String deviceProfile = association.getDeviceProfile();
|
||||
if (deviceProfile != null) {
|
||||
AssociationInfo otherAssociationWithDeviceProfile = find(
|
||||
getAllAssociationsForUser(association.getUserId()),
|
||||
mAssociationStore.getAssociationsForUser(association.getUserId()),
|
||||
a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
|
||||
if (otherAssociationWithDeviceProfile != null) {
|
||||
Slog.i(LOG_TAG, "Not revoking " + deviceProfile
|
||||
@@ -941,49 +918,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
.getPackageInfoAsUser(packageName, flags , userId));
|
||||
}
|
||||
|
||||
private void recordAssociation(AssociationInfo association, int userId) {
|
||||
Slog.i(LOG_TAG, "recordAssociation(" + association + ")");
|
||||
updateAssociations(associations -> add(associations, association), userId);
|
||||
}
|
||||
|
||||
private void updateAssociations(Function<Set<AssociationInfo>, Set<AssociationInfo>> update,
|
||||
int userId) {
|
||||
final List<AssociationInfo> associationList;
|
||||
synchronized (mLock) {
|
||||
if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set...");
|
||||
|
||||
final Set<AssociationInfo> prevAssociations = getAllAssociationsForUser(userId);
|
||||
if (DEBUG) Slog.d(LOG_TAG, " > Before : " + prevAssociations + "...");
|
||||
|
||||
final Set<AssociationInfo> updatedAssociations = update.apply(
|
||||
new ArraySet<>(prevAssociations));
|
||||
if (DEBUG) Slog.d(LOG_TAG, " > After: " + updatedAssociations);
|
||||
|
||||
associationList = new ArrayList<>(updatedAssociations);
|
||||
|
||||
mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations));
|
||||
|
||||
BackgroundThread.getHandler().sendMessage(
|
||||
PooledLambda.obtainMessage(
|
||||
(associations, usedIds) ->
|
||||
mPersistentDataStore
|
||||
.persistStateForUser(userId, associations, usedIds),
|
||||
updatedAssociations, deepCopy(mPreviouslyUsedIds.get(userId))));
|
||||
|
||||
updateAtm(userId, updatedAssociations);
|
||||
}
|
||||
|
||||
mListeners.broadcast((listener, callbackUserId) -> {
|
||||
if ((int) callbackUserId == userId) {
|
||||
try {
|
||||
listener.onAssociationsChanged(associationList);
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateAtm(int userId, Set<AssociationInfo> associations) {
|
||||
private void updateAtm(int userId, List<AssociationInfo> associations) {
|
||||
final Set<Integer> companionAppUids = new ArraySet<>();
|
||||
for (AssociationInfo association : associations) {
|
||||
final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
|
||||
@@ -998,50 +933,18 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
|
||||
if (mCachedAssociations.get(userId) != null) return;
|
||||
|
||||
Slog.i(LOG_TAG, "Reading state for user " + userId + " from the disk");
|
||||
|
||||
final Set<AssociationInfo> associations = new ArraySet<>();
|
||||
final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
|
||||
mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(LOG_TAG, " > associations=" + associations + "\n"
|
||||
+ " > previouslyUsedIds=" + previouslyUsedIds);
|
||||
}
|
||||
|
||||
mCachedAssociations.put(userId, unmodifiableSet(associations));
|
||||
mPreviouslyUsedIds.append(userId, previouslyUsedIds);
|
||||
}
|
||||
|
||||
private List<UserInfo> getAllUsers() {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mUserManager.getUsers();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
void onDeviceConnected(String address) {
|
||||
Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
|
||||
|
||||
mCurrentlyConnectedDevices.add(address);
|
||||
|
||||
for (UserInfo user : getAllUsers()) {
|
||||
for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
|
||||
if (association.isLinkedTo(address)) {
|
||||
if (association.getDeviceProfile() != null) {
|
||||
Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
|
||||
+ " to " + association.getPackageName()
|
||||
+ " due to device connected: " + association.getDeviceMacAddress());
|
||||
for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) {
|
||||
if (association.getDeviceProfile() != null) {
|
||||
Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
|
||||
+ " to " + association.getPackageName()
|
||||
+ " due to device connected: " + association.getDeviceMacAddress());
|
||||
|
||||
addRoleHolderForAssociation(getContext(), association);
|
||||
}
|
||||
}
|
||||
addRoleHolderForAssociation(getContext(), association);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,7 +1037,9 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
Date lastNearby = mDevicesLastNearby.valueAt(i);
|
||||
|
||||
if (isDeviceDisappeared(lastNearby)) {
|
||||
for (AssociationInfo association : getAllAssociations(address)) {
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(address);
|
||||
for (AssociationInfo association : associations) {
|
||||
if (association.isNotifyOnDeviceNearby()) {
|
||||
mCompanionDevicePresenceController.unbindDevicePresenceListener(
|
||||
association.getPackageName(), association.getUserId());
|
||||
@@ -1176,20 +1081,6 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<AssociationInfo> getAllAssociations(String deviceAddress) {
|
||||
List<UserInfo> aliveUsers = mUserManager.getAliveUsers();
|
||||
Set<AssociationInfo> result = new ArraySet<>();
|
||||
for (int i = 0, size = aliveUsers.size(); i < size; i++) {
|
||||
UserInfo user = aliveUsers.get(i);
|
||||
for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
|
||||
if (association.isLinkedTo(deviceAddress)) {
|
||||
result.add(association);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void onDeviceNearby(String address) {
|
||||
Date timestamp = new Date();
|
||||
Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
|
||||
@@ -1205,7 +1096,9 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
|| timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
|
||||
if (justAppeared) {
|
||||
Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
|
||||
for (AssociationInfo association : getAllAssociations(address)) {
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(address);
|
||||
for (AssociationInfo association : associations) {
|
||||
if (association.isNotifyOnDeviceNearby()) {
|
||||
mCompanionDevicePresenceController.onDeviceNotifyAppeared(association,
|
||||
getContext(), mMainHandler);
|
||||
@@ -1218,7 +1111,9 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")");
|
||||
|
||||
boolean hasDeviceListeners = false;
|
||||
for (AssociationInfo association : getAllAssociations(address)) {
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(address);
|
||||
for (AssociationInfo association : associations) {
|
||||
if (association.isNotifyOnDeviceNearby()) {
|
||||
mCompanionDevicePresenceController.onDeviceNotifyDisappeared(
|
||||
association, getContext(), mMainHandler);
|
||||
@@ -1291,7 +1186,7 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
private List<ScanFilter> getBleScanFilters() {
|
||||
ArrayList<ScanFilter> result = new ArrayList<>();
|
||||
ArraySet<String> addressesSeen = new ArraySet<>();
|
||||
for (AssociationInfo association : getAllAssociations()) {
|
||||
for (AssociationInfo association : mAssociationStore.getAssociations()) {
|
||||
if (association.isSelfManaged()) {
|
||||
continue;
|
||||
}
|
||||
@@ -1331,17 +1226,6 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
|
||||
if (getCallingUid() == SYSTEM_UID) return;
|
||||
|
||||
@@ -1356,4 +1240,61 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
+ FEATURE_COMPANION_DEVICE_SETUP
|
||||
+ " in manifest to use this API");
|
||||
}
|
||||
|
||||
private void registerPackageMonitor() {
|
||||
new PackageMonitor() {
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
|
||||
|
||||
clearAssociationForPackage(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageDataCleared(String packageName, int uid) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
|
||||
|
||||
clearAssociationForPackage(userId, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageModified(String packageName) {
|
||||
final int userId = getChangingUserId();
|
||||
Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
|
||||
|
||||
final List<AssociationInfo> associationsForPackage =
|
||||
mAssociationStore.getAssociationsForPackage(userId, packageName);
|
||||
for (AssociationInfo association : associationsForPackage) {
|
||||
updateSpecialAccessPermissionForAssociatedPackage(association);
|
||||
}
|
||||
}
|
||||
}.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
|
||||
}
|
||||
|
||||
private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
|
||||
if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
|
||||
|
||||
// First, unbind CompanionService if needed.
|
||||
mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
|
||||
|
||||
// Clear associations.
|
||||
final List<AssociationInfo> associationsForPackage =
|
||||
mAssociationStore.getAssociationsForPackage(userId, packageName);
|
||||
for (AssociationInfo association : associationsForPackage) {
|
||||
mAssociationStore.removeAssociation(association.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
|
||||
final Map<String, Set<Integer>> copy = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
|
||||
final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
|
||||
copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(copy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.server.companion;
|
||||
|
||||
import static com.android.internal.util.CollectionUtils.forEach;
|
||||
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
@@ -24,24 +23,33 @@ import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
class CompanionDeviceShellCommand extends android.os.ShellCommand {
|
||||
private final CompanionDeviceManagerService mService;
|
||||
private final AssociationStore mAssociationStore;
|
||||
|
||||
CompanionDeviceShellCommand(CompanionDeviceManagerService service) {
|
||||
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
|
||||
AssociationStore associationStore) {
|
||||
mService = service;
|
||||
mAssociationStore = associationStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
final PrintWriter out = getOutPrintWriter();
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "list": {
|
||||
forEach(
|
||||
mService.getAllAssociationsForUser(getNextArgInt()),
|
||||
a -> getOutPrintWriter()
|
||||
.println(a.getPackageName() + " "
|
||||
+ a.getDeviceMacAddress()));
|
||||
final int userId = getNextArgInt();
|
||||
final List<AssociationInfo> associationsForUser =
|
||||
mAssociationStore.getAssociationsForUser(userId);
|
||||
for (AssociationInfo association : associationsForUser) {
|
||||
// TODO(b/212535524): use AssociationInfo.toShortString(), once it's not
|
||||
// longer referenced in tests.
|
||||
out.println(association.getPackageName() + " "
|
||||
+ association.getDeviceMacAddress());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -60,7 +68,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand {
|
||||
final AssociationInfo association =
|
||||
mService.getAssociationWithCallerChecks(userId, packageName, address);
|
||||
if (association != null) {
|
||||
mService.disassociateInternal(userId, association.getId());
|
||||
mService.disassociateInternal(association.getId());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -33,11 +33,14 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.companion.AssociationInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.MacAddress;
|
||||
import android.os.Environment;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
@@ -51,7 +54,9 @@ import org.xmlpull.v1.XmlSerializer;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -68,8 +73,8 @@ import java.util.concurrent.ConcurrentMap;
|
||||
*
|
||||
* Before Android T the data was stored using the v0 schema.
|
||||
*
|
||||
* @see #readAssociationsV0(TypedXmlPullParser, int, Set)
|
||||
* @see #readAssociationV0(TypedXmlPullParser, int, int, Set)
|
||||
* @see #readAssociationsV0(TypedXmlPullParser, int, Collection)
|
||||
* @see #readAssociationV0(TypedXmlPullParser, int, int, Collection)
|
||||
*
|
||||
* The following snippet is a sample of a the file that is using v0 schema.
|
||||
* <pre>{@code
|
||||
@@ -100,8 +105,8 @@ import java.util.concurrent.ConcurrentMap;
|
||||
* optional.
|
||||
*
|
||||
* @see #CURRENT_PERSISTENCE_VERSION
|
||||
* @see #readAssociationsV1(TypedXmlPullParser, int, Set)
|
||||
* @see #readAssociationV1(TypedXmlPullParser, int, Set)
|
||||
* @see #readAssociationsV1(TypedXmlPullParser, int, Collection)
|
||||
* @see #readAssociationV1(TypedXmlPullParser, int, Collection)
|
||||
* @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map)
|
||||
*
|
||||
* The following snippet is a sample of a the file that is using v0 schema.
|
||||
@@ -168,6 +173,23 @@ final class PersistentDataStore {
|
||||
private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
void readStateForUsers(@NonNull List<UserInfo> users,
|
||||
@NonNull Set<AssociationInfo> allAssociationsOut,
|
||||
@NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
|
||||
for (UserInfo user : users) {
|
||||
final int userId = user.id;
|
||||
// Previously used IDs are stored in the "out" collection per-user.
|
||||
final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
|
||||
|
||||
// Associations for all users are stored in a single "flat" set: so we read directly
|
||||
// into it.
|
||||
readStateForUser(userId, allAssociationsOut, previouslyUsedIds);
|
||||
|
||||
// Save previously used IDs for this user into the "out" structure.
|
||||
previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads previously persisted data for the given user "into" the provided containers.
|
||||
*
|
||||
@@ -176,7 +198,7 @@ final class PersistentDataStore {
|
||||
* @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
|
||||
*/
|
||||
void readStateForUser(@UserIdInt int userId,
|
||||
@NonNull Set<AssociationInfo> associationsOut,
|
||||
@NonNull Collection<AssociationInfo> associationsOut,
|
||||
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
|
||||
Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
|
||||
final AtomicFile file = getStorageFileForUser(userId);
|
||||
@@ -237,7 +259,8 @@ final class PersistentDataStore {
|
||||
* @param associations a set of user's associations.
|
||||
* @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
|
||||
*/
|
||||
void persistStateForUser(@UserIdInt int userId, @NonNull Set<AssociationInfo> associations,
|
||||
void persistStateForUser(@UserIdInt int userId,
|
||||
@NonNull Collection<AssociationInfo> associations,
|
||||
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
|
||||
Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
|
||||
if (DEBUG) Slog.d(LOG_TAG, " > " + associations);
|
||||
@@ -250,7 +273,7 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
|
||||
@NonNull String rootTag, @Nullable Set<AssociationInfo> associationsOut,
|
||||
@NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
|
||||
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
|
||||
try (FileInputStream in = file.openRead()) {
|
||||
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
|
||||
@@ -282,7 +305,7 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private void persistStateToFileLocked(@NonNull AtomicFile file,
|
||||
@Nullable Set<AssociationInfo> associations,
|
||||
@Nullable Collection<AssociationInfo> associations,
|
||||
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
|
||||
file.write(out -> {
|
||||
try {
|
||||
@@ -321,7 +344,7 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
|
||||
@UserIdInt int userId, @NonNull Set<AssociationInfo> out)
|
||||
@UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
|
||||
throws XmlPullParserException, IOException {
|
||||
requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
|
||||
|
||||
@@ -342,7 +365,8 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
|
||||
int associationId, @NonNull Set<AssociationInfo> out) throws XmlPullParserException {
|
||||
int associationId, @NonNull Collection<AssociationInfo> out)
|
||||
throws XmlPullParserException {
|
||||
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
|
||||
|
||||
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
|
||||
@@ -360,7 +384,7 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
|
||||
@UserIdInt int userId, @NonNull Set<AssociationInfo> out)
|
||||
@UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
|
||||
throws XmlPullParserException, IOException {
|
||||
requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
|
||||
|
||||
@@ -374,7 +398,7 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
|
||||
@NonNull Set<AssociationInfo> out) throws XmlPullParserException, IOException {
|
||||
@NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException {
|
||||
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
|
||||
|
||||
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
|
||||
@@ -421,9 +445,11 @@ final class PersistentDataStore {
|
||||
}
|
||||
|
||||
private static void writeAssociations(@NonNull XmlSerializer parent,
|
||||
@Nullable Set<AssociationInfo> associations) throws IOException {
|
||||
@Nullable Collection<AssociationInfo> associations) throws IOException {
|
||||
final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
|
||||
forEach(associations, it -> writeAssociation(serializer, it));
|
||||
for (AssociationInfo association : associations) {
|
||||
writeAssociation(serializer, association);
|
||||
}
|
||||
serializer.endTag(null, XML_TAG_ASSOCIATIONS);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user