summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/companion/AssociationInfo.java8
-rw-r--r--services/companion/java/com/android/server/companion/BackupRestoreProcessor.java121
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java4
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java498
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java31
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java285
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java63
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java383
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java422
-rw-r--r--services/companion/java/com/android/server/companion/association/Associations.java68
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java218
-rw-r--r--services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java2
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java63
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java10
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java4
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java4
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java4
-rw-r--r--services/companion/java/com/android/server/companion/utils/DataStoreUtils.java4
-rw-r--r--services/companion/java/com/android/server/companion/utils/RolesUtils.java12
19 files changed, 1318 insertions, 886 deletions
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index b4b96e2c69d6..843158c0e9fb 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -252,14 +252,6 @@ public final class AssociationInfo implements Parcelable {
}
/**
- * @return true if the association is not revoked nor pending
- * @hide
- */
- public boolean isActive() {
- return !mRevoked && !mPending;
- }
-
- /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 5e52e06248cb..f2409fb8843e 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -18,8 +18,7 @@ package com.android.server.companion;
import static android.os.UserHandle.getCallingUserId;
-import static com.android.server.companion.association.AssociationDiskStore.readAssociationsFromPayload;
-import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -27,50 +26,62 @@ import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.Flags;
import android.companion.datatransfer.SystemDataTransferRequest;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
import com.android.server.companion.association.AssociationDiskStore;
import com.android.server.companion.association.AssociationRequestsProcessor;
import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.association.Associations;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Predicate;
@SuppressLint("LongLogTag")
class BackupRestoreProcessor {
- private static final String TAG = "CDM_BackupRestoreProcessor";
+ static final String TAG = "CDM_BackupRestoreProcessor";
private static final int BACKUP_AND_RESTORE_VERSION = 0;
- private final Context mContext;
@NonNull
- private final PackageManagerInternal mPackageManagerInternal;
+ private final CompanionDeviceManagerService mService;
+ @NonNull
+ private final PackageManagerInternal mPackageManager;
@NonNull
private final AssociationStore mAssociationStore;
@NonNull
- private final AssociationDiskStore mAssociationDiskStore;
+ private final AssociationDiskStore mPersistentStore;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
- BackupRestoreProcessor(@NonNull Context context,
- @NonNull PackageManagerInternal packageManagerInternal,
+ /**
+ * A structure that consists of a set of restored associations that are pending corresponding
+ * companion app to be installed.
+ */
+ @GuardedBy("mAssociationsPendingAppInstall")
+ private final PerUserAssociationSet mAssociationsPendingAppInstall =
+ new PerUserAssociationSet();
+
+ BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
@NonNull AssociationStore associationStore,
- @NonNull AssociationDiskStore associationDiskStore,
+ @NonNull AssociationDiskStore persistentStore,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull AssociationRequestsProcessor associationRequestsProcessor) {
- mContext = context;
- mPackageManagerInternal = packageManagerInternal;
+ mService = service;
+ mPackageManager = service.mPackageManagerInternal;
mAssociationStore = associationStore;
- mAssociationDiskStore = associationDiskStore;
+ mPersistentStore = persistentStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mAssociationRequestsProcessor = associationRequestsProcessor;
}
@@ -82,9 +93,9 @@ class BackupRestoreProcessor {
* | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)|
*/
byte[] getBackupPayload(int userId) {
- Slog.i(TAG, "getBackupPayload() userId=[" + userId + "].");
-
- byte[] associationsPayload = mAssociationDiskStore.getBackupPayload(userId);
+ // Persist state first to generate an up-to-date XML file
+ mService.persistStateForUser(userId);
+ byte[] associationsPayload = mPersistentStore.getBackupPayload(userId);
int associationsPayloadLength = associationsPayload.length;
// System data transfer requests are persisted up-to-date already
@@ -108,9 +119,6 @@ class BackupRestoreProcessor {
* Create new associations and system data transfer request consents using backed up payload.
*/
void applyRestoredPayload(byte[] payload, int userId) {
- Slog.i(TAG, "applyRestoredPayload() userId=[" + userId + "], payload size=["
- + payload.length + "].");
-
ByteBuffer buffer = ByteBuffer.wrap(payload);
// Make sure that payload version matches current version to ensure proper deserialization
@@ -123,8 +131,9 @@ class BackupRestoreProcessor {
// Read the bytes containing backed-up associations
byte[] associationsPayload = new byte[buffer.getInt()];
buffer.get(associationsPayload);
- final Associations restoredAssociations = readAssociationsFromPayload(
- associationsPayload, userId);
+ final Set<AssociationInfo> restoredAssociations = new HashSet<>();
+ mPersistentStore.readStateFromPayload(associationsPayload, userId,
+ restoredAssociations, new HashMap<>());
// Read the bytes containing backed-up system data transfer requests user consent
byte[] requestsPayload = new byte[buffer.getInt()];
@@ -133,13 +142,13 @@ class BackupRestoreProcessor {
mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
// Get a list of installed packages ahead of time.
- List<ApplicationInfo> installedApps = mPackageManagerInternal.getInstalledApplications(
+ List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
0, userId, getCallingUserId());
// Restored device may have a different user ID than the backed-up user's user-ID. Since
// association ID is dependent on the user ID, restored associations must account for
// this potential difference on their association IDs.
- for (AssociationInfo restored : restoredAssociations.getAssociations()) {
+ for (AssociationInfo restored : restoredAssociations) {
// Don't restore a revoked association. Since they weren't added to the device being
// restored in the first place, there is no need to worry about revoking a role that
// was never granted either.
@@ -159,9 +168,10 @@ class BackupRestoreProcessor {
// Create a new association reassigned to this user and a valid association ID
final String packageName = restored.getPackageName();
- final int newId = mAssociationStore.getNextId(userId);
- AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
- restored).build();
+ final int newId = mService.getNewAssociationIdForPackage(userId, packageName);
+ AssociationInfo newAssociation =
+ new AssociationInfo.Builder(newId, userId, packageName, restored)
+ .build();
// Check if the companion app for this association is already installed, then do one
// of the following:
@@ -169,15 +179,13 @@ class BackupRestoreProcessor {
// the role attached to this association to the app.
// (2) If the app isn't yet installed, then add this association to the list of pending
// associations to be added when the package is installed in the future.
- boolean isPackageInstalled = installedApps.stream().anyMatch(
- app -> packageName.equals(app.packageName));
+ boolean isPackageInstalled = installedApps.stream()
+ .anyMatch(app -> packageName.equals(app.packageName));
if (isPackageInstalled) {
mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
null, null);
} else {
- newAssociation = (new AssociationInfo.Builder(newAssociation)).setPending(true)
- .build();
- mAssociationStore.addAssociation(newAssociation);
+ addToPendingAppInstall(newAssociation);
}
// Re-map restored system data transfer requests to newly created associations
@@ -187,27 +195,32 @@ class BackupRestoreProcessor {
mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
}
}
+
+ // Persist restored state.
+ mService.persistStateForUser(userId);
+ }
+
+ void addToPendingAppInstall(@NonNull AssociationInfo association) {
+ association = (new AssociationInfo.Builder(association))
+ .setPending(true)
+ .build();
+
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).add(association);
+ }
}
- public void restorePendingAssociations(int userId, String packageName) {
- List<AssociationInfo> pendingAssociations = mAssociationStore.getPendingAssociations(userId,
- packageName);
- if (!pendingAssociations.isEmpty()) {
- Slog.i(TAG, "Found pending associations for package=[" + packageName
- + "]. Restoring...");
+ void removeFromPendingAppInstall(@NonNull AssociationInfo association) {
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).remove(association);
}
- for (AssociationInfo association : pendingAssociations) {
- AssociationInfo newAssociation = new AssociationInfo.Builder(association)
- .setPending(false)
- .build();
- addRoleHolderForAssociation(mContext, newAssociation, success -> {
- if (success) {
- mAssociationStore.updateAssociation(newAssociation);
- Slog.i(TAG, "Association=[" + association + "] is restored.");
- } else {
- Slog.e(TAG, "Failed to restore association=[" + association + "].");
- }
- });
+ }
+
+ @NonNull
+ Set<AssociationInfo> getAssociationsPendingAppInstallForUser(@UserIdInt int userId) {
+ synchronized (mAssociationsPendingAppInstall) {
+ // Return a copy.
+ return new ArraySet<>(mAssociationsPendingAppInstall.forUser(userId));
}
}
@@ -218,7 +231,7 @@ class BackupRestoreProcessor {
private boolean handleCollision(@UserIdInt int userId,
AssociationInfo restored,
List<SystemDataTransferRequest> restoredRequests) {
- List<AssociationInfo> localAssociations = mAssociationStore.getActiveAssociationsByPackage(
+ List<AssociationInfo> localAssociations = mAssociationStore.getAssociationsForPackage(
restored.getUserId(), restored.getPackageName());
Predicate<AssociationInfo> isSameDevice = associationInfo -> {
boolean matchesMacAddress = Objects.equals(
@@ -235,7 +248,7 @@ class BackupRestoreProcessor {
return false;
}
- Slog.d(TAG, "Conflict detected with association id=" + local.getId()
+ Log.d(TAG, "Conflict detected with association id=" + local.getId()
+ " while restoring CDM backup. Keeping local association.");
List<SystemDataTransferRequest> localRequests = mSystemDataTransferRequestStore
@@ -253,8 +266,8 @@ class BackupRestoreProcessor {
continue;
}
- Slog.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
- + " to an existing association id=[" + local.getId() + "].");
+ Log.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
+ + " to an existing association id=" + local.getId() + ".");
SystemDataTransferRequest newRequest =
restoredRequest.copyWithNewId(local.getId());
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 0a4148535451..c801489ce963 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -397,7 +397,7 @@ public class CompanionApplicationController {
// First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
if (isPrimary) {
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : associations) {
final String deviceProfile = association.getDeviceProfile();
@@ -442,7 +442,7 @@ public class CompanionApplicationController {
mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
+ mAssociationStore.getAssociationsForPackage(userId, packageName)) {
final int associationId = ai.getId();
stillAssociated = true;
if (ai.isSelfManaged()) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 73ebbc781c74..3846e981cbab 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,9 +37,12 @@ import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
-import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
@@ -79,16 +82,20 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
-import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
+import android.os.PowerWhitelistManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -98,9 +105,13 @@ import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.infra.PerUser;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
@@ -110,8 +121,8 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.association.AssociationDiskStore;
import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.association.InactiveAssociationsRemovalService;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
@@ -128,6 +139,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -152,51 +164,80 @@ public class CompanionDeviceManagerService extends SystemService {
private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private static final int MAX_CN_LENGTH = 500;
+ private final ActivityManager mActivityManager;
+ private AssociationDiskStore mAssociationDiskStore;
+ private final PersistUserStateHandler mUserPersistenceHandler;
+
+ private final AssociationStore mAssociationStore;
+ private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+ private AssociationRequestsProcessor mAssociationRequestsProcessor;
+ private SystemDataTransferProcessor mSystemDataTransferProcessor;
+ private BackupRestoreProcessor mBackupRestoreProcessor;
+ private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private CompanionApplicationController mCompanionAppController;
+ private CompanionTransportManager mTransportManager;
+ private AssociationRevokeProcessor mAssociationRevokeProcessor;
+
private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
private final IAppOpsService mAppOpsManager;
- private final PowerExemptionManager mPowerExemptionManager;
- private final PackageManagerInternal mPackageManagerInternal;
+ private final PowerWhitelistManager mPowerWhitelistManager;
+ private final UserManager mUserManager;
+ public final PackageManagerInternal mPackageManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
- private final AssociationStore mAssociationStore;
- private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
- private final ObservableUuidStore mObservableUuidStore;
- private final AssociationRequestsProcessor mAssociationRequestsProcessor;
- private final SystemDataTransferProcessor mSystemDataTransferProcessor;
- private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final CompanionApplicationController mCompanionAppController;
- private final CompanionTransportManager mTransportManager;
- private final DisassociationProcessor mDisassociationProcessor;
- private final CrossDeviceSyncController mCrossDeviceSyncController;
+ /**
+ * A structure that consists 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("mPreviouslyUsedIds")
+ private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
+
+ private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
+ new RemoteCallbackList<>();
+
+ private CrossDeviceSyncController mCrossDeviceSyncController;
+
+ private ObservableUuidStore mObservableUuidStore;
public CompanionDeviceManagerService(Context context) {
super(context);
- final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
+ mActivityManager = context.getSystemService(ActivityManager.class);
+ mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- final UserManager userManager = context.getSystemService(UserManager.class);
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mUserManager = context.getSystemService(UserManager.class);
- final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(userManager, associationDiskStore);
+ mUserPersistenceHandler = new PersistUserStateHandler();
+ mAssociationStore = new AssociationStore();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
+
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mObservableUuidStore = new ObservableUuidStore();
+ }
+
+ @Override
+ public void onStart() {
+ final Context context = getContext();
- // Init processors
- mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
- mPackageManagerInternal, mAssociationStore);
- mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
- mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
- mAssociationRequestsProcessor);
+ mAssociationDiskStore = new AssociationDiskStore();
+ mAssociationRequestsProcessor = new AssociationRequestsProcessor(
+ /* cdmService */ this, mAssociationStore);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(
+ /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
+ mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
+
+ mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
- mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
+ mAssociationStore.registerListener(mAssociationStoreChangeListener);
+
+ mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
mCompanionAppController = new CompanionApplicationController(
@@ -205,9 +246,11 @@ public class CompanionDeviceManagerService extends SystemService {
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
- mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
- mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
+ mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+ mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+ mSystemDataTransferRequestStore, mTransportManager);
+
+ loadAssociationsFromDisk();
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -215,16 +258,6 @@ public class CompanionDeviceManagerService extends SystemService {
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
- }
-
- @Override
- public void onStart() {
- // Init association stores
- mAssociationStore.refreshCache();
- mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
-
- // Init UUID store
- mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
// Publish "binder" service.
final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -234,6 +267,50 @@ public class CompanionDeviceManagerService extends SystemService {
LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService());
}
+ void loadAssociationsFromDisk() {
+ final Set<AssociationInfo> allAssociations = new ArraySet<>();
+ synchronized (mPreviouslyUsedIds) {
+ List<Integer> userIds = new ArrayList<>();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ userIds.add(user.id);
+ }
+ // 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).
+ mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
+ }
+
+ final Set<AssociationInfo> activeAssociations =
+ new ArraySet<>(/* capacity */ allAssociations.size());
+ // A set contains the userIds that need to persist state after remove the app
+ // from the list of role holders.
+ final Set<Integer> usersToPersistStateFor = new ArraySet<>();
+
+ for (AssociationInfo association : allAssociations) {
+ if (association.isPending()) {
+ mBackupRestoreProcessor.addToPendingAppInstall(association);
+ } else if (!association.isRevoked()) {
+ activeAssociations.add(association);
+ } else if (mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(
+ association)) {
+ // Nothing more to do here, but we'll need to persist all the associations to the
+ // disk afterwards.
+ usersToPersistStateFor.add(association.getUserId());
+ } else {
+ mAssociationRevokeProcessor.addToPendingRoleHolderRemoval(association);
+ }
+ }
+
+ mAssociationStore.setAssociationsToCache(activeAssociations);
+
+ // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
+ // persistStateForUser() queries AssociationStore.
+ // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it
+ // would effectively just clear-out all the persisted associations).
+ for (int userId : usersToPersistStateFor) {
+ persistStateForUser(userId);
+ }
+ }
+
@Override
public void onBootPhase(int phase) {
final Context context = getContext();
@@ -252,10 +329,8 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- Slog.d(TAG, "onUserUnlocking...");
final int userId = user.getUserIdentifier();
- final List<AssociationInfo> associations = mAssociationStore.getActiveAssociationsByUser(
- userId);
+ final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId);
if (associations.isEmpty()) return;
@@ -284,8 +359,7 @@ public class CompanionDeviceManagerService extends SystemService {
? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByAddress(
- bluetoothDevice.getAddress())) {
+ mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
}
@@ -305,7 +379,7 @@ public class CompanionDeviceManagerService extends SystemService {
@NonNull
AssociationInfo getAssociationWithCallerChecks(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, macAddress);
association = sanitizeWithCallerChecks(getContext(), association);
if (association != null) {
@@ -459,7 +533,7 @@ public class CompanionDeviceManagerService extends SystemService {
*/
private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> packageAssociations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
@@ -477,6 +551,77 @@ public class CompanionDeviceManagerService extends SystemService {
return false;
}
+ private void onAssociationChangedInternal(
+ @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);
+
+ mUserPersistenceHandler.postPersistUserState(userId);
+
+ // 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);
+ }
+
+ void persistStateForUser(@UserIdInt int userId) {
+ // We want to store both active associations and the revoked (removed) association that we
+ // are keeping around for the final clean-up (delayed role holder removal).
+ final List<AssociationInfo> allAssociations;
+ // Start with the active associations - these we can get from the AssociationStore.
+ allAssociations = new ArrayList<>(
+ mAssociationStore.getAssociationsForUser(userId));
+ // ... and add the revoked (removed) association, that are yet to be permanently removed.
+ allAssociations.addAll(
+ mAssociationRevokeProcessor.getPendingRoleHolderRemovalAssociationsForUser(userId));
+ // ... and add the restored associations that are pending missing package installation.
+ allAssociations.addAll(mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId));
+
+ final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
+
+ mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
+ }
+
+ private void notifyListeners(
+ @UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
+ mListeners.broadcast((listener, callbackUserId) -> {
+ int listenerUserId = (int) callbackUserId;
+ if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) {
+ 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);
+ }
+ }
+
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) {
@@ -484,20 +629,19 @@ public class CompanionDeviceManagerService extends SystemService {
+ packageName);
}
- // Clear all associations for the package.
+ // Clear associations.
final List<AssociationInfo> associationsForPackage =
- mAssociationStore.getAssociationsByPackage(userId, packageName);
- if (!associationsForPackage.isEmpty()) {
- Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
- + packageName + "]. Cleaning up CDM data...");
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+ for (AssociationInfo association : associationsForPackage) {
+ mAssociationStore.removeAssociation(association.getId());
}
+ // Clear role holders
for (AssociationInfo association : associationsForPackage) {
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(association);
}
-
- // Clear observable UUIDs for the package.
- final List<ObservableUuid> uuidsTobeObserved =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+ // Clear the uuids to be observed.
for (ObservableUuid uuid : uuidsTobeObserved) {
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
@@ -508,13 +652,31 @@ public class CompanionDeviceManagerService extends SystemService {
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName);
- updateSpecialAccessPermissionForAssociatedPackage(userId, packageName);
+ final List<AssociationInfo> associationsForPackage =
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
+ for (AssociationInfo association : associationsForPackage) {
+ updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
+ }
mCompanionAppController.onPackagesChanged(userId);
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
- mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
+ if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
+
+ Set<AssociationInfo> associationsPendingAppInstall = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo association : associationsPendingAppInstall) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ AssociationInfo newAssociation = new AssociationInfo.Builder(association)
+ .setPending(false)
+ .build();
+ mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+ null, null);
+ mBackupRestoreProcessor.removeFromPendingAppInstall(association);
+ }
}
// Revoke associations if the selfManaged companion device does not connect for 3 months.
@@ -536,7 +698,7 @@ public class CompanionDeviceManagerService extends SystemService {
final int id = association.getId();
Slog.i(TAG, "Removing inactive self-managed association id=" + id);
- mDisassociationProcessor.disassociate(id);
+ mAssociationRevokeProcessor.disassociateInternal(id);
}
}
@@ -588,7 +750,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
}
- return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ return mAssociationStore.getAssociationsForPackage(userId, packageName);
}
@Override
@@ -599,9 +761,9 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
if (userId == UserHandle.USER_ALL) {
- return mAssociationStore.getActiveAssociations();
+ return List.copyOf(mAssociationStore.getAssociations());
}
- return mAssociationStore.getActiveAssociationsByUser(userId);
+ return mAssociationStore.getAssociationsForUser(userId);
}
@Override
@@ -611,8 +773,7 @@ public class CompanionDeviceManagerService extends SystemService {
addOnAssociationsChangedListener_enforcePermission();
enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-
- mAssociationStore.registerRemoteListener(listener, userId);
+ mListeners.register(listener, userId);
}
@Override
@@ -623,7 +784,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
- mAssociationStore.unregisterRemoteListener(listener);
+ mListeners.unregister(listener);
}
@Override
@@ -682,16 +843,16 @@ public class CompanionDeviceManagerService extends SystemService {
final AssociationInfo association =
getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationRevokeProcessor.disassociateInternal(association.getId());
}
@Override
public void disassociate(int associationId) {
- Slog.i(TAG, "disassociate() associationId=" + associationId);
+ Log.i(TAG, "disassociate() associationId=" + associationId);
final AssociationInfo association =
getAssociationWithCallerChecks(associationId);
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationRevokeProcessor.disassociateInternal(association.getId());
}
@Override
@@ -706,7 +867,8 @@ public class CompanionDeviceManagerService extends SystemService {
throw new IllegalArgumentException("Component name is too long.");
}
- return Binder.withCleanCallingIdentity(() -> {
+ final long identity = Binder.clearCallingIdentity();
+ try {
if (!isRestrictedSettingsAllowed(getContext(), callingPackage, callingUid)) {
Slog.e(TAG, "Side loaded app must enable restricted "
+ "setting before request the notification access");
@@ -720,7 +882,9 @@ public class CompanionDeviceManagerService extends SystemService {
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
new UserHandle(userId));
- });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
/**
@@ -748,7 +912,7 @@ public class CompanionDeviceManagerService extends SystemService {
return true;
}
- return any(mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
+ return any(mAssociationStore.getAssociationsForPackage(userId, packageName),
a -> a.isLinkedTo(macAddress));
}
@@ -1002,7 +1166,7 @@ public class CompanionDeviceManagerService extends SystemService {
final int userId = getCallingUserId();
enforceCallerIsSystemOr(userId, packageName);
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, deviceAddress);
if (association == null) {
@@ -1075,15 +1239,14 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
checkState(!ArrayUtils.isEmpty(
- mAssociationStore.getActiveAssociationsByPackage(userId,
- callingPackage)),
+ mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
"App must have an association before calling this API");
}
@Override
public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
final AssociationInfo association =
- mAssociationStore.getFirstAssociationByAddress(
+ mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, macAddress);
if (association == null) {
return false;
@@ -1106,11 +1269,13 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public byte[] getBackupPayload(int userId) {
+ Log.i(TAG, "getBackupPayload() userId=" + userId);
return mBackupRestoreProcessor.getBackupPayload(userId);
}
@Override
public void applyRestoredPayload(byte[] payload, int userId) {
+ Log.i(TAG, "applyRestoredPayload() userId=" + userId);
mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
}
@@ -1121,7 +1286,7 @@ public class CompanionDeviceManagerService extends SystemService {
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
mAssociationStore, mDevicePresenceMonitor, mTransportManager,
mSystemDataTransferProcessor, mAssociationRequestsProcessor,
- mBackupRestoreProcessor, mDisassociationProcessor)
+ mBackupRestoreProcessor, mAssociationRevokeProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
@@ -1149,6 +1314,88 @@ public class CompanionDeviceManagerService extends SystemService {
/* callback */ null, /* resultReceiver */ null);
}
+ @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) {
+ // "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;
+ }
+
+ /**
+ * Get a new association id for the package.
+ */
+ public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mPreviouslyUsedIds) {
+ // First: collect all IDs currently in use for this user's Associations.
+ final SparseBooleanArray usedIds = new SparseBooleanArray();
+
+ // We should really only be checking associations for the given user (i.e.:
+ // mAssociationStore.getAssociationsForUser(userId)), BUT in the past we've got in a
+ // state where association IDs were not assigned correctly in regard to
+ // user-to-association-ids-range (e.g. associations with IDs from 1 to 100,000 should
+ // always belong to u0), so let's check all the associations.
+ for (AssociationInfo it : mAssociationStore.getAssociations()) {
+ usedIds.put(it.getId(), true);
+ }
+
+ // Some IDs may be reserved by associations that aren't stored yet due to missing
+ // package after a backup restoration. We don't want the ID to have been taken by
+ // another association by the time when it is activated from the package installation.
+ final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo it : pendingAssociations) {
+ usedIds.put(it.getId(), 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;
+ }
+ }
+
/**
* Update special access for the association's package
*/
@@ -1156,27 +1403,20 @@ public class CompanionDeviceManagerService extends SystemService {
final PackageInfo packageInfo =
getPackageInfo(getContext(), userId, packageName);
- Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo,
- userId, packageName));
+ Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
}
- private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo, int userId,
- String packageName) {
+ private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
if (packageInfo == null) {
return;
}
-
- List<AssociationInfo> associations = mAssociationStore.getActiveAssociationsByPackage(
- userId, packageName);
-
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
- && !associations.isEmpty()) {
- mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+ android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
+ mPowerWhitelistManager.addToWhitelist(packageInfo.packageName);
} else {
try {
- mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+ mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName);
} catch (UnsupportedOperationException e) {
Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+ " whitelist. It might due to the package is whitelisted by the system.");
@@ -1187,8 +1427,7 @@ public class CompanionDeviceManagerService extends SystemService {
try {
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.USE_DATA_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
- && !associations.isEmpty()) {
+ android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
networkPolicyManager.addUidPolicy(
packageInfo.applicationInfo.uid,
NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
@@ -1248,7 +1487,7 @@ public class CompanionDeviceManagerService extends SystemService {
try {
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByUser(userId);
+ mAssociationStore.getAssociationsForUser(userId);
for (AssociationInfo a : associations) {
try {
int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
@@ -1267,16 +1506,7 @@ public class CompanionDeviceManagerService extends SystemService {
new AssociationStore.OnChangeListener() {
@Override
public void onAssociationChanged(int changeType, AssociationInfo association) {
- Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
- + "], association=[" + association);
-
- final int userId = association.getUserId();
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getActiveAssociationsByUser(userId);
-
- updateAtm(userId, updatedAssociations);
- updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
- association.getPackageName());
+ onAssociationChangedInternal(changeType, association);
}
};
@@ -1404,4 +1634,64 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
}
+
+ /**
+ * This method must only be called from {@link CompanionDeviceShellCommand} for testing
+ * purposes only!
+ */
+ void persistState() {
+ mUserPersistenceHandler.clearMessages();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ persistStateForUser(user.id);
+ }
+ }
+
+ /**
+ * This class is dedicated to handling requests to persist user state.
+ */
+ @SuppressLint("HandlerLeak")
+ private class PersistUserStateHandler extends Handler {
+ PersistUserStateHandler() {
+ super(BackgroundThread.get().getLooper());
+ }
+
+ /**
+ * Persists user state unless there is already an outstanding request for the given user.
+ */
+ synchronized void postPersistUserState(@UserIdInt int userId) {
+ if (!hasMessages(userId)) {
+ sendMessage(obtainMessage(userId));
+ }
+ }
+
+ /**
+ * Clears *ALL* outstanding persist requests for *ALL* users.
+ */
+ synchronized void clearMessages() {
+ removeCallbacksAndMessages(null);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int userId = msg.what;
+ persistStateForUser(userId);
+ }
+ }
+
+ /**
+ * Persist associations
+ */
+ public void postPersistUserState(@UserIdInt int userId) {
+ mUserPersistenceHandler.postPersistUserState(userId);
+ }
+
+ /**
+ * Set to store associations
+ */
+ public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ @Override
+ protected @NonNull Set<AssociationInfo> create(int userId) {
+ return new ArraySet<>();
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a7a73cb6bddb..16877dcaf183 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -33,8 +33,8 @@ import android.util.Base64;
import android.util.proto.ProtoOutputStream;
import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
@@ -49,7 +49,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
private static final String TAG = "CDM_CompanionDeviceShellCommand";
private final CompanionDeviceManagerService mService;
- private final DisassociationProcessor mDisassociationProcessor;
+ private final AssociationRevokeProcessor mRevokeProcessor;
private final AssociationStore mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
@@ -65,7 +65,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
BackupRestoreProcessor backupRestoreProcessor,
- DisassociationProcessor disassociationProcessor) {
+ AssociationRevokeProcessor revokeProcessor) {
mService = service;
mAssociationStore = associationStore;
mDevicePresenceMonitor = devicePresenceMonitor;
@@ -73,7 +73,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
mBackupRestoreProcessor = backupRestoreProcessor;
- mDisassociationProcessor = disassociationProcessor;
+ mRevokeProcessor = revokeProcessor;
}
@Override
@@ -105,15 +105,12 @@ class CompanionDeviceShellCommand extends ShellCommand {
case "list": {
final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
- mAssociationStore.getActiveAssociationsByUser(userId);
- final int maxId = mAssociationStore.getMaxId(userId);
- out.println("Max ID: " + maxId);
- out.println("Association ID | Package Name | Mac Address");
+ mAssociationStore.getAssociationsForUser(userId);
for (AssociationInfo association : associationsForUser) {
// TODO(b/212535524): use AssociationInfo.toShortString(), once it's not
// longer referenced in tests.
- out.println(association.getId() + " | " + association.getPackageName()
- + " | " + association.getDeviceMacAddress());
+ out.println(association.getPackageName() + " "
+ + association.getDeviceMacAddress() + " " + association.getId());
}
}
break;
@@ -135,24 +132,28 @@ class CompanionDeviceShellCommand extends ShellCommand {
final String address = getNextArgRequired();
final AssociationInfo association =
mService.getAssociationWithCallerChecks(userId, packageName, address);
- mDisassociationProcessor.disassociate(association.getId());
+ if (association != null) {
+ mRevokeProcessor.disassociateInternal(association.getId());
+ }
}
break;
case "disassociate-all": {
final int userId = getNextIntArgRequired();
+ final String packageName = getNextArgRequired();
final List<AssociationInfo> userAssociations =
- mAssociationStore.getAssociationsByUser(userId);
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : userAssociations) {
if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
- mDisassociationProcessor.disassociate(association.getId());
+ mRevokeProcessor.disassociateInternal(association.getId());
}
}
}
break;
- case "refresh-cache":
- mAssociationStore.refreshCache();
+ case "clear-association-memory-cache":
+ mService.persistState();
+ mService.loadAssociationsFromDisk();
break;
case "simulate-device-appeared":
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9c8504..75cb12058247 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -16,6 +16,7 @@
package com.android.server.companion.association;
+import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -25,6 +26,7 @@ import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
@@ -38,8 +40,10 @@ import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
import android.os.Environment;
+import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
@@ -55,9 +59,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.HashMap;
+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;
import java.util.concurrent.ConcurrentMap;
@@ -76,8 +82,8 @@ import java.util.concurrent.ConcurrentMap;
* <p>
* Before Android T the data was stored using the v0 schema. See:
* <ul>
- * <li>{@link #readAssociationsV0(TypedXmlPullParser, int) readAssociationsV0()}.
- * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int) readAssociationV0()}.
+ * <li>{@link #readAssociationsV0(TypedXmlPullParser, int, Collection) readAssociationsV0()}.
+ * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int, Collection) readAssociationV0()}.
* </ul>
*
* The following snippet is a sample of a file that is using v0 schema.
@@ -110,14 +116,15 @@ import java.util.concurrent.ConcurrentMap;
* optional.
* <ul>
* <li> {@link #CURRENT_PERSISTENCE_VERSION}
- * <li> {@link #readAssociationsV1(TypedXmlPullParser, int) readAssociationsV1()}
- * <li> {@link #readAssociationV1(TypedXmlPullParser, int) readAssociationV1()}
+ * <li> {@link #readAssociationsV1(TypedXmlPullParser, int, Collection) readAssociationsV1()}
+ * <li> {@link #readAssociationV1(TypedXmlPullParser, int, Collection) readAssociationV1()}
+ * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
* </ul>
*
* The following snippet is a sample of a file that is using v1 schema.
* <pre>{@code
* <state persistence-version="1">
- * <associations max-id="3">
+ * <associations>
* <association
* id="1"
* package="com.sample.companion.app"
@@ -141,12 +148,18 @@ import java.util.concurrent.ConcurrentMap;
* time_approved="1634641160229"
* system_data_sync_flags="1"/>
* </associations>
+ *
+ * <previously-used-ids>
+ * <package package_name="com.sample.companion.app">
+ * <id>2</id>
+ * </package>
+ * </previously-used-ids>
* </state>
* }</pre>
*/
@SuppressLint("LongLogTag")
public final class AssociationDiskStore {
- private static final String TAG = "CDM_AssociationDiskStore";
+ private static final String TAG = "CompanionDevice_AssociationDiskStore";
private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -156,11 +169,16 @@ public final class AssociationDiskStore {
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
private static final String XML_TAG_ASSOCIATION = "association";
+ 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_TAG = "tag";
+ private static final String XML_TAG_ID = "id";
private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
- private static final String XML_ATTR_MAX_ID = "max-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_MAC_ADDRESS = "mac_address";
private static final String XML_ATTR_DISPLAY_NAME = "display_name";
@@ -181,12 +199,38 @@ public final class AssociationDiskStore {
/**
* Read all associations for given users
*/
- public Map<Integer, Associations> readAssociationsByUsers(@NonNull List<Integer> userIds) {
- Map<Integer, Associations> userToAssociationsMap = new HashMap<>();
+ public void readStateForUsers(@NonNull List<Integer> userIds,
+ @NonNull Set<AssociationInfo> allAssociationsOut,
+ @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
for (int userId : userIds) {
- userToAssociationsMap.put(userId, readAssociationsByUser(userId));
+ // 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.
+ final Set<AssociationInfo> associationsForUser = new HashSet<>();
+ readStateForUser(userId, associationsForUser, previouslyUsedIds);
+
+ // Go through all the associations for the user and check if their IDs are within
+ // the allowed range (for the user).
+ final int firstAllowedId = getFirstAssociationIdForUser(userId);
+ final int lastAllowedId = getLastAssociationIdForUser(userId);
+ for (AssociationInfo association : associationsForUser) {
+ final int id = association.getId();
+ if (id < firstAllowedId || id > lastAllowedId) {
+ Slog.e(TAG, "Wrong association ID assignment: " + id + ". "
+ + "Association belongs to u" + userId + " and thus its ID should be "
+ + "within [" + firstAllowedId + ", " + lastAllowedId + "] range.");
+ // TODO(b/224736262): try fixing (re-assigning) the ID?
+ }
+ }
+
+ // Add user's association to the "output" set.
+ allAssociationsOut.addAll(associationsForUser);
+
+ // Save previously used IDs for this user into the "out" structure.
+ previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds);
}
- return userToAssociationsMap;
}
/**
@@ -196,12 +240,16 @@ public final class AssociationDiskStore {
* retrieval from this datastore because it is not persisted (by design). This means that
* persisted data is not guaranteed to be identical to the initial data that was stored at the
* time of association.
+ *
+ * @param userId Android UserID
+ * @param associationsOut a container to read the {@link AssociationInfo}s "into".
+ * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
*/
- @NonNull
- private Associations readAssociationsByUser(@UserIdInt int userId) {
- Slog.i(TAG, "Reading associations for user " + userId + " from disk.");
+ private void readStateForUser(@UserIdInt int userId,
+ @NonNull Collection<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+ Slog.i(TAG, "Reading associations for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
- Associations associations;
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
@@ -212,7 +260,7 @@ public final class AssociationDiskStore {
if (!file.getBaseFile().exists()) {
legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
if (!legacyBaseFile.exists()) {
- return new Associations();
+ return;
}
readFrom = new AtomicFile(legacyBaseFile);
@@ -222,12 +270,13 @@ public final class AssociationDiskStore {
rootTag = XML_TAG_STATE;
}
- associations = readAssociationsFromFile(userId, readFrom, rootTag);
+ final int version = readStateFromFileLocked(userId, readFrom, rootTag,
+ associationsOut, previouslyUsedIdsPerPackageOut);
- if (legacyBaseFile != null || associations.getVersion() < CURRENT_PERSISTENCE_VERSION) {
+ if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
// The data is either in the legacy file or in the legacy format, or both.
// Save the data to right file in using the current format.
- writeAssociationsToFile(file, associations);
+ persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
if (legacyBaseFile != null) {
// We saved the data to the right file, can delete the old file now.
@@ -235,75 +284,89 @@ public final class AssociationDiskStore {
}
}
}
- return associations;
}
/**
- * Write associations to disk for the user.
+ * Persisted data to the disk.
+ *
+ * Note that associatedDevice field in {@link AssociationInfo} is not persisted by this
+ * datastore implementation.
+ *
+ * @param userId Android UserID
+ * @param associations a set of user's associations.
+ * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
*/
- public void writeAssociationsForUser(@UserIdInt int userId,
- @NonNull Associations associations) {
+ public void persistStateForUser(@UserIdInt int userId,
+ @NonNull Collection<AssociationInfo> associations,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
Slog.i(TAG, "Writing associations for user " + userId + " to disk");
final AtomicFile file = getStorageFileForUser(userId);
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
synchronized (file) {
- writeAssociationsToFile(file, associations);
+ persistStateToFileLocked(file, associations, previouslyUsedIdsPerPackage);
}
}
- @NonNull
- private static Associations readAssociationsFromFile(@UserIdInt int userId,
- @NonNull AtomicFile file, @NonNull String rootTag) {
+ private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file,
+ @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (FileInputStream in = file.openRead()) {
- return readAssociationsFromInputStream(userId, in, rootTag);
+ return readStateFromInputStream(userId, in, rootTag, associationsOut,
+ previouslyUsedIdsPerPackageOut);
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Error while reading associations file", e);
- return new Associations();
+ return -1;
}
}
- @NonNull
- private static Associations readAssociationsFromInputStream(@UserIdInt int userId,
- @NonNull InputStream in, @NonNull String rootTag)
+ private int readStateFromInputStream(@UserIdInt int userId, @NonNull InputStream in,
+ @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut)
throws XmlPullParserException, IOException {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- XmlUtils.beginDocument(parser, rootTag);
+ XmlUtils.beginDocument(parser, rootTag);
final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
- Associations associations = new Associations();
-
switch (version) {
case 0:
- associations = readAssociationsV0(parser, userId);
+ readAssociationsV0(parser, userId, associationsOut);
break;
case 1:
while (true) {
parser.nextTag();
if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
- associations = readAssociationsV1(parser, userId);
+ readAssociationsV1(parser, userId, associationsOut);
+ } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
+ readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
} else if (isEndOfTag(parser, rootTag)) {
break;
}
}
break;
}
- return associations;
+ return version;
}
- private void writeAssociationsToFile(@NonNull AtomicFile file,
- @NonNull Associations associations) {
+ private void persistStateToFileLocked(@NonNull AtomicFile file,
+ @Nullable Collection<AssociationInfo> associations,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
// Writing to file could fail, for example, if the user has been recently removed and so was
// their DE (/data/system_de/<user-id>/) directory.
writeToFileSafely(file, out -> {
final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_STATE);
writeIntAttribute(serializer,
XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
+
writeAssociations(serializer, associations);
+ writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage);
+
serializer.endTag(null, XML_TAG_STATE);
serializer.endDocument();
});
@@ -316,8 +379,7 @@ public final class AssociationDiskStore {
* IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
* possible to synchronize reads and writes to the file using the returned object.
*/
- @NonNull
- private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+ private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
return mUserIdToStorageFile.computeIfAbsent(userId,
u -> createStorageFileForUser(userId, FILE_NAME));
}
@@ -337,12 +399,14 @@ public final class AssociationDiskStore {
/**
* Convert payload to a set of associations
*/
- public static Associations readAssociationsFromPayload(byte[] payload, @UserIdInt int userId) {
+ public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+ @NonNull Set<AssociationInfo> associationsOut,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
- return readAssociationsFromInputStream(userId, in, XML_TAG_STATE);
+ readStateFromInputStream(userId, in, XML_TAG_STATE, associationsOut,
+ previouslyUsedIdsPerPackageOut);
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Error while reading associations file", e);
- return new Associations();
}
}
@@ -350,8 +414,8 @@ public final class AssociationDiskStore {
return new File(Environment.getUserSystemDirectory(userId), FILE_NAME_LEGACY);
}
- private static Associations readAssociationsV0(@NonNull TypedXmlPullParser parser,
- @UserIdInt int userId)
+ private static void readAssociationsV0(@NonNull TypedXmlPullParser parser,
+ @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
throws XmlPullParserException, IOException {
requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
@@ -362,70 +426,52 @@ public final class AssociationDiskStore {
// 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 = getFirstAssociationIdForUser(userId);
- Associations associations = new Associations();
- associations.setVersion(0);
-
while (true) {
parser.nextTag();
if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
- associations.addAssociation(readAssociationV0(parser, userId, associationId++));
+ readAssociationV0(parser, userId, associationId++, out);
}
-
- associations.setMaxId(associationId - 1);
-
- return associations;
}
- private static AssociationInfo readAssociationV0(@NonNull TypedXmlPullParser parser,
- @UserIdInt int userId, int associationId)
+ private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
+ int associationId, @NonNull Collection<AssociationInfo> out)
throws XmlPullParserException {
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
final String tag = readStringAttribute(parser, XML_TAG_TAG);
final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
+
+ if (appPackage == null || deviceAddress == null) return;
+
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- return new AssociationInfo(associationId, userId, appPackage, tag,
+ out.add(new AssociationInfo(associationId, userId, appPackage, tag,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
}
- private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
- @UserIdInt int userId)
+ private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
+ @UserIdInt int userId, @NonNull Collection<AssociationInfo> out)
throws XmlPullParserException, IOException {
requireStartOfTag(parser, XML_TAG_ASSOCIATIONS);
- // For old builds that don't have max-id attr,
- // default maxId to 0 and get the maxId out of all association ids.
- int maxId = readIntAttribute(parser, XML_ATTR_MAX_ID, 0);
- Associations associations = new Associations();
- associations.setVersion(1);
-
while (true) {
parser.nextTag();
if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;
- AssociationInfo association = readAssociationV1(parser, userId);
- associations.addAssociation(association);
-
- maxId = Math.max(maxId, association.getId());
+ readAssociationV1(parser, userId, out);
}
-
- associations.setMaxId(maxId);
-
- return associations;
}
- private static AssociationInfo readAssociationV1(@NonNull TypedXmlPullParser parser,
- @UserIdInt int userId)
- throws XmlPullParserException, IOException {
+ private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId,
+ @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException {
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
@@ -445,19 +491,46 @@ public final class AssociationDiskStore {
final int systemDataSyncFlags = readIntAttribute(parser,
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
- return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
- profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags);
+ final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
+ appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
+ pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
+ if (associationInfo != null) {
+ out.add(associationInfo);
+ }
+ }
+
+ private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
+ @NonNull Map<String, Set<Integer>> out) throws XmlPullParserException, IOException {
+ requireStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS);
+
+ 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,
- @NonNull Associations associations)
- throws IOException {
+ @Nullable Collection<AssociationInfo> associations) throws IOException {
final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
- for (AssociationInfo association : associations.getAssociations()) {
+ for (AssociationInfo association : associations) {
writeAssociation(serializer, association);
}
- writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
serializer.endTag(null, XML_TAG_ASSOCIATIONS);
}
@@ -484,6 +557,26 @@ public final class AssociationDiskStore {
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
+ private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
+ @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException {
+ 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 void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws XmlPullParserException {
if (isStartOfTag(parser, tag)) return;
@@ -494,4 +587,22 @@ public final class AssociationDiskStore {
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
return address != null ? MacAddress.fromString(address) : null;
}
+
+ private static AssociationInfo createAssociationInfoNoThrow(int associationId,
+ @UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
+ boolean pending, long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
+ AssociationInfo associationInfo = null;
+ try {
+ // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
+ // datastore is not guaranteed to be identical to the one from initial association.
+ associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
+ macAddress, displayName, profile, null, selfManaged, notify,
+ revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not create AssociationInfo", e);
+ }
+ return associationInfo;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a02d9f912bcd..29ec7c2c9743 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -24,6 +24,7 @@ import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
@@ -127,16 +128,17 @@ public class AssociationRequestsProcessor {
private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
private final @NonNull Context mContext;
- private final @NonNull PackageManagerInternal mPackageManagerInternal;
+ private final @NonNull CompanionDeviceManagerService mService;
+ private final @NonNull PackageManagerInternal mPackageManager;
private final @NonNull AssociationStore mAssociationStore;
@NonNull
private final ComponentName mCompanionDeviceActivity;
- public AssociationRequestsProcessor(@NonNull Context context,
- @NonNull PackageManagerInternal packageManagerInternal,
+ public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
@NonNull AssociationStore associationStore) {
- mContext = context;
- mPackageManagerInternal = packageManagerInternal;
+ mContext = service.getContext();
+ mService = service;
+ mPackageManager = service.mPackageManagerInternal;
mAssociationStore = associationStore;
mCompanionDeviceActivity = createRelative(
mContext.getString(R.string.config_companionDeviceManagerPackage),
@@ -158,7 +160,7 @@ public class AssociationRequestsProcessor {
requireNonNull(packageName, "Package name MUST NOT be null");
requireNonNull(callback, "Callback MUST NOT be null");
- final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
+ userId + "/" + packageName + " (uid=" + packageUid + ")");
@@ -224,7 +226,7 @@ public class AssociationRequestsProcessor {
enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
- final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
final Bundle extras = new Bundle();
extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
@@ -241,7 +243,7 @@ public class AssociationRequestsProcessor {
@NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) {
final String packageName = request.getPackageName();
final int userId = request.getUserId();
- final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
// 1. Need to check permissions again in case something changed, since we first received
// this request.
@@ -265,12 +267,15 @@ public class AssociationRequestsProcessor {
@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
@Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback,
@NonNull ResultReceiver resultReceiver) {
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
request.isSelfManaged(),
callback, resultReceiver);
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
}
/**
@@ -281,7 +286,7 @@ public class AssociationRequestsProcessor {
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
- final int id = mAssociationStore.getNextId(userId);
+ final int id = mService.getNewAssociationIdForPackage(userId, packageName);
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
@@ -291,6 +296,10 @@ public class AssociationRequestsProcessor {
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+
+ // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
+ // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
+ // that there are other devices with the same profile, so the role holder won't be removed.
}
/**
@@ -302,12 +311,12 @@ public class AssociationRequestsProcessor {
// If the "Device Profile" is specified, make the companion application a holder of the
// corresponding role.
// If it is null, then the operation will succeed without granting any role.
- addRoleHolderForAssociation(mContext, association, success -> {
+ addRoleHolderForAssociation(mService.getContext(), association, success -> {
if (success) {
Slog.i(TAG, "Added " + association.getDeviceProfile() + " role to userId="
+ association.getUserId() + ", packageName="
+ association.getPackageName());
- mAssociationStore.addAssociation(association);
+ addAssociationToStore(association);
sendCallbackAndFinish(association, callback, resultReceiver);
} else {
Slog.e(TAG, "Failed to add u" + association.getUserId()
@@ -338,6 +347,17 @@ public class AssociationRequestsProcessor {
mAssociationStore.updateAssociation(updated);
}
+ private void addAssociationToStore(@NonNull AssociationInfo association) {
+ Slog.i(TAG, "New CDM association created=" + association);
+
+ mAssociationStore.addAssociation(association);
+
+ mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
+
+ logCreateAssociation(association.getDeviceProfile());
+ }
+
private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -389,22 +409,27 @@ public class AssociationRequestsProcessor {
private PendingIntent createPendingIntent(int packageUid, Intent intent) {
final PendingIntent pendingIntent;
+ final long token = Binder.clearCallingIdentity();
// Using uid of the application that will own the association (usually the same
// application that sent the request) allows us to have multiple "pending" association
// requests at the same time.
// If the application already has a pending association request, that PendingIntent
// will be cancelled except application wants to cancel the request by the system.
- return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(
+ try {
+ pendingIntent = PendingIntent.getActivityAsUser(
mContext, /*requestCode */ packageUid, intent,
FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
ActivityOptions.makeBasic()
.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
.toBundle(),
- UserHandle.CURRENT)
- );
+ UserHandle.CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return pendingIntent;
}
private final ResultReceiver mOnRequestConfirmationReceiver =
@@ -445,7 +470,7 @@ public class AssociationRequestsProcessor {
// Throttle frequent associations
final long now = System.currentTimeMillis();
final List<AssociationInfo> associationForPackage =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
// Number of "recent" associations.
int recent = 0;
for (AssociationInfo association : associationForPackage) {
@@ -461,6 +486,6 @@ public class AssociationRequestsProcessor {
}
}
- return PackageUtils.isPackageAllowlisted(mContext, mPackageManagerInternal, packageName);
+ return PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName);
}
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
new file mode 100644
index 000000000000..d1efbbcd3411
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2024 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.association;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
+import static com.android.internal.util.CollectionUtils.any;
+import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.transport.CompanionTransportManager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class response for Association removal.
+ */
+@SuppressLint("LongLogTag")
+public class AssociationRevokeProcessor {
+
+ private static final String TAG = "CDM_AssociationRevokeProcessor";
+ private static final boolean DEBUG = false;
+ private final @NonNull Context mContext;
+ private final @NonNull CompanionDeviceManagerService mService;
+ private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull PackageManagerInternal mPackageManagerInternal;
+ private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+ private final @NonNull CompanionApplicationController mCompanionAppController;
+ private final @NonNull CompanionTransportManager mTransportManager;
+ private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+ private final ActivityManager mActivityManager;
+
+ /**
+ * A structure that consists of a set of revoked associations that pending for role holder
+ * removal per each user.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
+ new PerUserAssociationSet();
+ /**
+ * Contains uid-s of packages pending to be removed from the role holder list (after
+ * revocation of an association), which will happen one the package is no longer visible to the
+ * user.
+ * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
+ * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
+ * from uid-s using {@link UserHandle#getUserId(int)}).
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
+
+ public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore,
+ @NonNull PackageManagerInternal packageManager,
+ @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
+ @NonNull CompanionApplicationController applicationController,
+ @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+ @NonNull CompanionTransportManager companionTransportManager) {
+ mService = service;
+ mContext = service.getContext();
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mAssociationStore = associationStore;
+ mPackageManagerInternal = packageManager;
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
+ mDevicePresenceMonitor = devicePresenceMonitor;
+ mCompanionAppController = applicationController;
+ mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+ mTransportManager = companionTransportManager;
+ }
+
+ /**
+ * Disassociate an association
+ */
+ // TODO: also revoke notification access
+ public void disassociateInternal(int associationId) {
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final String deviceProfile = association.getDeviceProfile();
+
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Need to remove the app from list of the role holders, but will have to do it later
+ // (the app is in foreground at the moment).
+ addToPendingRoleHolderRemoval(association);
+ }
+
+ // Need to check if device still present now because CompanionDevicePresenceMonitor will
+ // remove current connected device after mAssociationStore.removeAssociation
+ final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
+
+ // Removing the association.
+ mAssociationStore.removeAssociation(associationId);
+ // Do not need to persistUserState since CompanionDeviceManagerService will get callback
+ // from #onAssociationChanged, and it will handle the persistUserState which including
+ // active and revoked association.
+ logRemoveAssociation(deviceProfile);
+
+ // Remove all the system data transfer requests for the association.
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
+
+ if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
+ // The device was connected and the app was notified: check if we need to unbind the app
+ // now.
+ final boolean shouldStayBound = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> it.isNotifyOnDeviceNearby()
+ && mDevicePresenceMonitor.isDevicePresent(it.getId()));
+ if (shouldStayBound) return;
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
+
+ /**
+ * First, checks if the companion application should be removed from the list role holders when
+ * upon association's removal, i.e.: association's profile (matches the role) is not null,
+ * the application does not have other associations with the same profile, etc.
+ *
+ * <p>
+ * Then, if establishes that the application indeed has to be removed from the list of the role
+ * holders, checks if it could be done right now -
+ * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
+ * will kill the application's process, which leads poor user experience if the application was
+ * in foreground when this happened, to avoid this CDMS delays invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
+ *
+ * @return {@code true} if the application does NOT need be removed from the list of the role
+ * holders OR if the application was successfully removed from the list of role holders.
+ * I.e.: from the role-management perspective the association is done with.
+ * {@code false} if the application needs to be removed from the list of role the role
+ * holders, BUT it CDMS would prefer to do it later.
+ * I.e.: application is in the foreground at the moment, but invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
+ * which would lead to the poor UX, hence need to try later.
+ */
+ public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
+ final String deviceProfile = association.getDeviceProfile();
+
+ if (deviceProfile == null) {
+ // No role was granted to for this association, there is nothing else we need to here.
+ return true;
+ }
+ // Do not need to remove the system role since it was pre-granted by the system.
+ if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+ return true;
+ }
+
+ // Check if the applications is associated with another devices with the profile. If so,
+ // it should remain the role holder.
+ final int id = association.getId();
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final boolean roleStillInUse = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+ if (roleStillInUse) {
+ // Application should remain a role holder, there is nothing else we need to here.
+ return true;
+ }
+
+ final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+ if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
+ // Need to remove the app from the list of role holders, but the process is visible to
+ // the user at the moment, so we'll need to it later: log and return false.
+ Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
+ + " now - process is visible.");
+ return false;
+ }
+
+ removeRoleHolderForAssociation(mContext, association.getUserId(),
+ association.getPackageName(), association.getDeviceProfile());
+ return true;
+ }
+
+ /**
+ * Set revoked flag for active association and add the revoked association and the uid into
+ * the caches.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ // First: set revoked flag
+ association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ // Second: add to the set.
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
+ .add(association);
+ if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
+ mUidsPendingRoleHolderRemoval.put(uid, packageName);
+
+ if (mUidsPendingRoleHolderRemoval.size() == 1) {
+ // Just added first uid: start the listener
+ mOnPackageVisibilityChangeListener.startListening();
+ }
+ }
+ }
+ }
+
+ /**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ @NonNull
+ public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
+ * Remove the revoked association from the cache and also remove the uid from the map if
+ * there are other associations with the same package still pending for role holder removal.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */ 0, userId);
+
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
+ .remove(association);
+
+ final boolean shouldKeepUidForRemoval = any(
+ getPendingRoleHolderRemovalAssociationsForUser(userId),
+ ai -> packageName.equals(ai.getPackageName()));
+ // Do not remove the uid from the map since other associations with
+ // the same packageName still pending for role holder removal.
+ if (!shouldKeepUidForRemoval) {
+ mUidsPendingRoleHolderRemoval.remove(uid);
+ }
+
+ if (mUidsPendingRoleHolderRemoval.isEmpty()) {
+ // The set is empty now - can "turn off" the listener.
+ mOnPackageVisibilityChangeListener.stopListening();
+ }
+ }
+ }
+
+ private String getPackageNameByUid(int uid) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ return mUidsPendingRoleHolderRemoval.get(uid);
+ }
+ }
+
+ /**
+ * An OnUidImportanceListener class which watches the importance of the packages.
+ * In this class, we ONLY interested in the importance of the running process is greater than
+ * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE} for the uids have been added
+ * into the {@link #mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the
+ * revoked associations for the same packages.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ private class OnPackageVisibilityChangeListener implements
+ ActivityManager.OnUidImportanceListener {
+ final @NonNull ActivityManager mAm;
+
+ OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
+ this.mAm = am;
+ }
+
+ @SuppressLint("MissingPermission")
+ void startListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.addOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+ }
+
+ @SuppressLint("MissingPermission")
+ void stopListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.removeOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this));
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+ // The lower the importance value the more "important" the process is.
+ // We are only interested when the process ceases to be visible.
+ return;
+ }
+
+ final String packageName = getPackageNameByUid(uid);
+ if (packageName == null) {
+ // Not interested in this uid.
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+
+ boolean needToPersistStateForUser = false;
+
+ for (AssociationInfo association :
+ getPendingRoleHolderRemovalAssociationsForUser(userId)) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Did not remove the role holder, will have to try again later.
+ continue;
+ }
+
+ removeFromPendingRoleHolderRemoval(association);
+ needToPersistStateForUser = true;
+ }
+
+ if (needToPersistStateForUser) {
+ mService.postPersistUserState(userId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 29de764c4d5f..2f94bdebb988 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -16,24 +16,15 @@
package com.android.server.companion.association;
-import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
-import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
-import android.companion.IOnAssociationsChangedListener;
-import android.content.pm.UserInfo;
import android.net.MacAddress;
-import android.os.Binder;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
@@ -42,14 +33,15 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
/**
* Association store for CRUD.
@@ -117,105 +109,44 @@ public class AssociationStore {
private final Object mLock = new Object();
- private final ExecutorService mExecutor;
-
@GuardedBy("mLock")
- private boolean mPersisted = false;
+ private final Map<Integer, AssociationInfo> mIdMap = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
+ private final Map<MacAddress, Set<Integer>> mAddressMap = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
-
- @GuardedBy("mLocalListeners")
- private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
- @GuardedBy("mRemoteListeners")
- private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
- new RemoteCallbackList<>();
-
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
-
- public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
- mUserManager = userManager;
- mDiskStore = diskStore;
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- /**
- * Load all alive users' associations from disk to cache.
- */
- public void refreshCache() {
- Binder.withCleanCallingIdentity(() -> {
- List<Integer> userIds = new ArrayList<>();
- for (UserInfo user : mUserManager.getAliveUsers()) {
- userIds.add(user.id);
- }
-
- synchronized (mLock) {
- mPersisted = false;
-
- mIdToAssociationMap.clear();
- mUserToMaxId.clear();
-
- // 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).
- Map<Integer, Associations> userToAssociationsMap =
- mDiskStore.readAssociationsByUsers(userIds);
- for (Map.Entry<Integer, Associations> entry : userToAssociationsMap.entrySet()) {
- for (AssociationInfo association : entry.getValue().getAssociations()) {
- mIdToAssociationMap.put(association.getId(), association);
- }
- mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
- }
-
- mPersisted = true;
- }
- });
- }
-
- /**
- * Get the current max association id.
- */
- public int getMaxId(int userId) {
- synchronized (mLock) {
- return mUserToMaxId.getOrDefault(userId, 0);
- }
- }
+ private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>();
- /**
- * Get the next available association id.
- */
- public int getNextId(int userId) {
- synchronized (mLock) {
- return getMaxId(userId) + 1;
- }
- }
+ @GuardedBy("mListeners")
+ private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
/**
* Add an association.
*/
public void addAssociation(@NonNull AssociationInfo association) {
- Slog.i(TAG, "Adding new association=[" + association + "]...");
+ Slog.i(TAG, "Adding new association=" + association);
+
+ // Validity check first.
+ checkNotRevoked(association);
final int id = association.getId();
- final int userId = association.getUserId();
synchronized (mLock) {
- if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association with id=[" + id + "] already exists.");
+ if (mIdMap.containsKey(id)) {
+ Slog.e(TAG, "Association with id " + id + " already exists.");
return;
}
+ mIdMap.put(id, association);
- mIdToAssociationMap.put(id, association);
- mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
+ final MacAddress address = association.getDeviceMacAddress();
+ if (address != null) {
+ mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ }
- writeCacheToDisk(userId);
+ invalidateCacheForUserLocked(association.getUserId());
Slog.i(TAG, "Done adding new association.");
}
- logCreateAssociation(association.getDeviceProfile());
-
broadcastChange(CHANGE_TYPE_ADDED, association);
}
@@ -223,16 +154,18 @@ public class AssociationStore {
* Update an association.
*/
public void updateAssociation(@NonNull AssociationInfo updated) {
- Slog.i(TAG, "Updating new association=[" + updated + "]...");
+ Slog.i(TAG, "Updating new association=" + updated);
+ // Validity check first.
+ checkNotRevoked(updated);
final int id = updated.getId();
+
final AssociationInfo current;
final boolean macAddressChanged;
-
synchronized (mLock) {
- current = mIdToAssociationMap.get(id);
+ current = mIdMap.get(id);
if (current == null) {
- Slog.w(TAG, "Can't update association id=[" + id + "]. It does not exist.");
+ Slog.w(TAG, "Can't update association. It does not exist.");
return;
}
@@ -241,238 +174,174 @@ public class AssociationStore {
return;
}
- mIdToAssociationMap.put(id, updated);
-
- writeCacheToDisk(updated.getUserId());
+ // Update the ID-to-Association map.
+ mIdMap.put(id, updated);
+ // Invalidate the corresponding user cache entry.
+ invalidateCacheForUserLocked(current.getUserId());
+
+ // Update the MacAddress-to-List<Association> map if needed.
+ final MacAddress updatedAddress = updated.getDeviceMacAddress();
+ final MacAddress currentAddress = current.getDeviceMacAddress();
+ macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
+ if (macAddressChanged) {
+ if (currentAddress != null) {
+ mAddressMap.get(currentAddress).remove(id);
+ }
+ if (updatedAddress != null) {
+ mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
+ }
+ }
+ Slog.i(TAG, "Done updating association.");
}
- Slog.i(TAG, "Done updating association.");
-
- // Check if the MacAddress has changed.
- final MacAddress updatedAddress = updated.getDeviceMacAddress();
- final MacAddress currentAddress = current.getDeviceMacAddress();
- macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
-
final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
: CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-
broadcastChange(changeType, updated);
}
/**
- * Remove an association.
+ * Remove an association
*/
public void removeAssociation(int id) {
- Slog.i(TAG, "Removing association id=[" + id + "]...");
+ Slog.i(TAG, "Removing association id=" + id);
final AssociationInfo association;
-
synchronized (mLock) {
- association = mIdToAssociationMap.remove(id);
+ association = mIdMap.remove(id);
if (association == null) {
- Slog.w(TAG, "Can't remove association id=[" + id + "]. It does not exist.");
+ Slog.w(TAG, "Can't remove association. It does not exist.");
return;
}
- writeCacheToDisk(association.getUserId());
-
- Slog.i(TAG, "Done removing association.");
- }
-
- logRemoveAssociation(association.getDeviceProfile());
-
- broadcastChange(CHANGE_TYPE_REMOVED, association);
- }
-
- private void writeCacheToDisk(@UserIdInt int userId) {
- mExecutor.execute(() -> {
- Associations associations = new Associations();
- synchronized (mLock) {
- associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
- associations.setAssociations(
- CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
- a -> a.getUserId() == userId));
+ final MacAddress macAddress = association.getDeviceMacAddress();
+ if (macAddress != null) {
+ mAddressMap.get(macAddress).remove(id);
}
- mDiskStore.writeAssociationsForUser(userId, associations);
- });
- }
- /**
- * Get a copy of all associations including pending and revoked ones.
- * Modifying the copy won't modify the actual associations.
- *
- * If a cache miss happens, read from disk.
- */
- @NonNull
- public List<AssociationInfo> getAssociations() {
- synchronized (mLock) {
- if (!mPersisted) {
- refreshCache();
- }
- return List.copyOf(mIdToAssociationMap.values());
- }
- }
+ invalidateCacheForUserLocked(association.getUserId());
- /**
- * Get a copy of active associations.
- */
- @NonNull
- public List<AssociationInfo> getActiveAssociations() {
- synchronized (mLock) {
- return CollectionUtils.filter(getAssociations(), AssociationInfo::isActive);
+ Slog.i(TAG, "Done removing association.");
}
- }
- /**
- * Get a copy of all associations by user.
- */
- @NonNull
- public List<AssociationInfo> getAssociationsByUser(@UserIdInt int userId) {
- synchronized (mLock) {
- return CollectionUtils.filter(getAssociations(), a -> a.getUserId() == userId);
- }
+ broadcastChange(CHANGE_TYPE_REMOVED, association);
}
/**
- * Get a copy of active associations by user.
+ * @return a "snapshot" of the current state of the existing associations.
*/
- @NonNull
- public List<AssociationInfo> getActiveAssociationsByUser(@UserIdInt int userId) {
+ public @NonNull Collection<AssociationInfo> getAssociations() {
synchronized (mLock) {
- return CollectionUtils.filter(getActiveAssociations(), a -> a.getUserId() == userId);
+ // IMPORTANT: make and return a COPY of the mIdMap.values(), NOT a "direct" reference.
+ // The HashMap.values() returns a collection which is backed by the HashMap, so changes
+ // to the HashMap are reflected in this collection.
+ // For us this means that if mIdMap is modified while the iteration over mIdMap.values()
+ // is in progress it may lead to "undefined results" (according to the HashMap's
+ // documentation) or cause ConcurrentModificationExceptions in the iterator (according
+ // to the bugreports...).
+ return List.copyOf(mIdMap.values());
}
}
/**
- * Get a copy of all associations by package.
+ * Get associations for the user.
*/
- @NonNull
- public List<AssociationInfo> getAssociationsByPackage(@UserIdInt int userId,
- @NonNull String packageName) {
+ public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
synchronized (mLock) {
- return CollectionUtils.filter(getAssociationsByUser(userId),
- a -> a.getPackageName().equals(packageName));
+ return getAssociationsForUserLocked(userId);
}
}
/**
- * Get a copy of active associations by package.
+ * Get associations for the package
*/
- @NonNull
- public List<AssociationInfo> getActiveAssociationsByPackage(@UserIdInt int userId,
- @NonNull String packageName) {
- synchronized (mLock) {
- return CollectionUtils.filter(getActiveAssociationsByUser(userId),
- a -> a.getPackageName().equals(packageName));
- }
+ 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);
}
/**
- * Get the first active association with the mac address.
+ * Get associations by mac address for the package.
*/
- @Nullable
- public AssociationInfo getFirstAssociationByAddress(
+ public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- synchronized (mLock) {
- return CollectionUtils.find(getActiveAssociationsByPackage(userId, packageName),
- a -> a.getDeviceMacAddress() != null && a.getDeviceMacAddress()
- .equals(MacAddress.fromString(macAddress)));
- }
+ final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
+ return CollectionUtils.find(associations,
+ it -> it.belongsToPackage(userId, packageName));
}
/**
- * Get the association by id.
+ * Get association by id.
*/
- @Nullable
- public AssociationInfo getAssociationById(int id) {
+ public @Nullable AssociationInfo getAssociationById(int id) {
synchronized (mLock) {
- return mIdToAssociationMap.get(id);
+ return mIdMap.get(id);
}
}
/**
- * Get a copy of active associations by mac address.
+ * Get associations by mac address.
*/
@NonNull
- public List<AssociationInfo> getActiveAssociationsByAddress(@NonNull String macAddress) {
- synchronized (mLock) {
- return CollectionUtils.filter(getActiveAssociations(),
- a -> a.getDeviceMacAddress() != null && a.getDeviceMacAddress()
- .equals(MacAddress.fromString(macAddress)));
- }
- }
+ public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+ final MacAddress address = MacAddress.fromString(macAddress);
- /**
- * Get a copy of revoked associations.
- */
- @NonNull
- public List<AssociationInfo> getRevokedAssociations() {
synchronized (mLock) {
- return CollectionUtils.filter(getAssociations(), AssociationInfo::isRevoked);
- }
- }
+ final Set<Integer> ids = mAddressMap.get(address);
+ if (ids == null) return Collections.emptyList();
- /**
- * Get a copy of revoked associations for the package.
- */
- @NonNull
- public List<AssociationInfo> getRevokedAssociations(@UserIdInt int userId,
- @NonNull String packageName) {
- synchronized (mLock) {
- return CollectionUtils.filter(getAssociations(),
- a -> packageName.equals(a.getPackageName()) && a.getUserId() == userId
- && a.isRevoked());
+ final List<AssociationInfo> associations = new ArrayList<>(ids.size());
+ for (Integer id : ids) {
+ associations.add(mIdMap.get(id));
+ }
+
+ return Collections.unmodifiableList(associations);
}
}
- /**
- * Get a copy of active associations.
- */
+ @GuardedBy("mLock")
@NonNull
- public List<AssociationInfo> getPendingAssociations(@UserIdInt int userId,
- @NonNull String packageName) {
- synchronized (mLock) {
- return CollectionUtils.filter(getAssociations(),
- a -> packageName.equals(a.getPackageName()) && a.getUserId() == userId
- && a.isPending());
+ private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+ final List<AssociationInfo> cached = mCachedPerUser.get(userId);
+ if (cached != null) {
+ return cached;
}
- }
- /**
- * Register a local listener for association changes.
- */
- public void registerLocalListener(@NonNull OnChangeListener listener) {
- synchronized (mLocalListeners) {
- mLocalListeners.add(listener);
+ 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;
}
- /**
- * Unregister a local listener previously registered for association changes.
- */
- public void unregisterLocalListener(@NonNull OnChangeListener listener) {
- synchronized (mLocalListeners) {
- mLocalListeners.remove(listener);
- }
+ @GuardedBy("mLock")
+ private void invalidateCacheForUserLocked(@UserIdInt int userId) {
+ mCachedPerUser.delete(userId);
}
/**
- * Register a remote listener for association changes.
+ * Register a listener for association changes.
*/
- public void registerRemoteListener(@NonNull IOnAssociationsChangedListener listener,
- int userId) {
- synchronized (mRemoteListeners) {
- mRemoteListeners.register(listener, userId);
+ public void registerListener(@NonNull OnChangeListener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
}
}
/**
- * Unregister a remote listener previously registered for association changes.
+ * Unregister a listener previously registered for association changes.
*/
- public void unregisterRemoteListener(@NonNull IOnAssociationsChangedListener listener) {
- synchronized (mRemoteListeners) {
- mRemoteListeners.unregister(listener);
+ public void unregisterListener(@NonNull OnChangeListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
}
}
@@ -481,39 +350,52 @@ public class AssociationStore {
*/
public void dump(@NonNull PrintWriter out) {
out.append("Companion Device Associations: ");
- if (getActiveAssociations().isEmpty()) {
+ if (getAssociations().isEmpty()) {
out.append("<empty>\n");
} else {
out.append("\n");
- for (AssociationInfo a : getActiveAssociations()) {
+ for (AssociationInfo a : getAssociations()) {
out.append(" ").append(a.toString()).append('\n');
}
}
}
private void broadcastChange(@ChangeType int changeType, AssociationInfo association) {
- synchronized (mLocalListeners) {
- for (OnChangeListener listener : mLocalListeners) {
+ synchronized (mListeners) {
+ for (OnChangeListener listener : mListeners) {
listener.onAssociationChanged(changeType, association);
}
}
- synchronized (mRemoteListeners) {
- final int userId = association.getUserId();
- final List<AssociationInfo> updatedAssociations = getActiveAssociationsByUser(userId);
- // 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) {
- mRemoteListeners.broadcast((listener, callbackUserId) -> {
- int listenerUserId = (int) callbackUserId;
- if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) {
- try {
- listener.onAssociationsChanged(updatedAssociations);
- } catch (RemoteException ignored) {
- }
- }
- });
+ }
+
+ /**
+ * Set associations to cache. It will clear the existing cache.
+ */
+ public void setAssociationsToCache(Collection<AssociationInfo> associations) {
+ // Validity check first.
+ associations.forEach(AssociationStore::checkNotRevoked);
+
+ synchronized (mLock) {
+ mIdMap.clear();
+ mAddressMap.clear();
+ mCachedPerUser.clear();
+
+ 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);
+ }
}
}
}
+
+ private static void checkNotRevoked(@NonNull AssociationInfo association) {
+ if (association.isRevoked()) {
+ throw new IllegalArgumentException(
+ "Revoked (removed) associations MUST NOT appear in the AssociationStore");
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/association/Associations.java b/services/companion/java/com/android/server/companion/association/Associations.java
deleted file mode 100644
index 7da3699dba8d..000000000000
--- a/services/companion/java/com/android/server/companion/association/Associations.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 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.association;
-
-import android.companion.AssociationInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents associations per user. Should be only used by Association stores.
- */
-public class Associations {
-
- private int mVersion = 0;
-
- private List<AssociationInfo> mAssociations = new ArrayList<>();
-
- private int mMaxId = 0;
-
- public Associations() {
- }
-
- public void setVersion(int version) {
- mVersion = version;
- }
-
- /**
- * Add an association.
- */
- public void addAssociation(AssociationInfo association) {
- mAssociations.add(association);
- }
-
- public void setMaxId(int maxId) {
- mMaxId = maxId;
- }
-
- public void setAssociations(List<AssociationInfo> associations) {
- mAssociations = List.copyOf(associations);
- }
-
- public int getVersion() {
- return mVersion;
- }
-
- public int getMaxId() {
- return mMaxId;
- }
-
- public List<AssociationInfo> getAssociations() {
- return mAssociations;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
deleted file mode 100644
index ec8977918c56..000000000000
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2024 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.association;
-
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-
-import static com.android.internal.util.CollectionUtils.any;
-import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.server.companion.CompanionApplicationController;
-import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.transport.CompanionTransportManager;
-
-/**
- * A class response for Association removal.
- */
-@SuppressLint("LongLogTag")
-public class DisassociationProcessor {
-
- private static final String TAG = "CDM_DisassociationProcessor";
- @NonNull
- private final Context mContext;
- @NonNull
- private final AssociationStore mAssociationStore;
- @NonNull
- private final PackageManagerInternal mPackageManagerInternal;
- @NonNull
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- @NonNull
- private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
- @NonNull
- private final CompanionApplicationController mCompanionAppController;
- @NonNull
- private final CompanionTransportManager mTransportManager;
- private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
- private final ActivityManager mActivityManager;
-
- public DisassociationProcessor(@NonNull Context context,
- @NonNull ActivityManager activityManager,
- @NonNull AssociationStore associationStore,
- @NonNull PackageManagerInternal packageManager,
- @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
- @NonNull CompanionApplicationController applicationController,
- @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
- @NonNull CompanionTransportManager companionTransportManager) {
- mContext = context;
- mActivityManager = activityManager;
- mAssociationStore = associationStore;
- mPackageManagerInternal = packageManager;
- mOnPackageVisibilityChangeListener =
- new OnPackageVisibilityChangeListener();
- mDevicePresenceMonitor = devicePresenceMonitor;
- mCompanionAppController = applicationController;
- mSystemDataTransferRequestStore = systemDataTransferRequestStore;
- mTransportManager = companionTransportManager;
- }
-
- /**
- * Disassociate an association by id.
- */
- // TODO: also revoke notification access
- public void disassociate(int id) {
- Slog.i(TAG, "Disassociating id=[" + id + "]...");
-
- final AssociationInfo association = mAssociationStore.getAssociationById(id);
- if (association == null) {
- Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
- return;
- }
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final String deviceProfile = association.getDeviceProfile();
-
- final boolean isRoleInUseByOtherAssociations = deviceProfile != null
- && any(mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
- it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
-
- final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
- if (packageProcessImportance <= IMPORTANCE_VISIBLE && deviceProfile != null
- && !isRoleInUseByOtherAssociations) {
- // Need to remove the app from the list of role holders, but the process is visible
- // to the user at the moment, so we'll need to do it later.
- Slog.i(TAG, "Cannot disassociate id=[" + id + "] now - process is visible. "
- + "Start listening to package importance...");
-
- AssociationInfo revokedAssociation = (new AssociationInfo.Builder(
- association)).setRevoked(true).build();
- mAssociationStore.updateAssociation(revokedAssociation);
- startListening();
- return;
- }
-
- // Association cleanup.
- mAssociationStore.removeAssociation(association.getId());
- mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
-
- // Detach transport if exists
- mTransportManager.detachSystemDataTransport(packageName, userId, id);
-
- // If role is not in use by other associations, revoke the role.
- // Do not need to remove the system role since it was pre-granted by the system.
- if (!isRoleInUseByOtherAssociations && deviceProfile != null && !deviceProfile.equals(
- DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
- removeRoleHolderForAssociation(mContext, association.getUserId(),
- association.getPackageName(), association.getDeviceProfile());
- }
-
- // Unbind the app if needed.
- final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(id);
- if (!wasPresent || !association.isNotifyOnDeviceNearby()) {
- return;
- }
- final boolean shouldStayBound = any(
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
- it -> it.isNotifyOnDeviceNearby()
- && mDevicePresenceMonitor.isDevicePresent(it.getId()));
- if (!shouldStayBound) {
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
- }
-
- @SuppressLint("MissingPermission")
- private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
- return Binder.withCleanCallingIdentity(() -> {
- final int uid =
- mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
- return mActivityManager.getUidImportance(uid);
- });
- }
-
- private void startListening() {
- Slog.i(TAG, "Start listening to uid importance changes...");
- try {
- Binder.withCleanCallingIdentity(
- () -> mActivityManager.addOnUidImportanceListener(
- mOnPackageVisibilityChangeListener,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to start listening to uid importance changes.");
- }
- }
-
- private void stopListening() {
- Slog.i(TAG, "Stop listening to uid importance changes.");
- try {
- Binder.withCleanCallingIdentity(() -> mActivityManager.removeOnUidImportanceListener(
- mOnPackageVisibilityChangeListener));
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to stop listening to uid importance changes.");
- }
- }
-
- /**
- * An OnUidImportanceListener class which watches the importance of the packages.
- * In this class, we ONLY interested in the importance of the running process is greater than
- * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
- *
- * Lastly remove the role holder for the revoked associations for the same packages.
- *
- * @see #disassociate(int)
- */
- private class OnPackageVisibilityChangeListener implements
- ActivityManager.OnUidImportanceListener {
-
- @Override
- public void onUidImportance(int uid, int importance) {
- if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
- // The lower the importance value the more "important" the process is.
- // We are only interested when the process ceases to be visible.
- return;
- }
-
- final String packageName = mPackageManagerInternal.getNameForUid(uid);
- if (packageName == null) {
- // Not interested in this uid.
- return;
- }
-
- int userId = UserHandle.getUserId(uid);
- for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
- packageName)) {
- disassociate(association.getId());
- }
-
- if (mAssociationStore.getRevokedAssociations().isEmpty()) {
- stopListening();
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index f28731548dcc..894c49a2b5cf 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -33,7 +33,7 @@ import com.android.server.companion.CompanionDeviceManagerServiceInternal;
* A Job Service responsible for clean up idle self-managed associations.
*
* The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked. See {@link DisassociationProcessor}
+ * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
*/
public class InactiveAssociationsRemovalService extends JobService {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index c5ca0bf7e9c5..a08e0da90d49 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -186,14 +186,18 @@ public class SystemDataTransferProcessor {
intent.putExtras(extras);
// Create a PendingIntent
- return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId,
- intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- ActivityOptions.makeBasic()
- .setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .toBundle(),
- UserHandle.CURRENT));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -224,7 +228,8 @@ public class SystemDataTransferProcessor {
}
// Start permission sync
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
// TODO: refactor to work with streams of data
mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
mExecutor, backup -> {
@@ -232,31 +237,39 @@ public class SystemDataTransferProcessor {
.requestPermissionRestore(associationId, backup);
translateFutureToCallback(future, callback);
});
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
/**
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
int userId = mAssociationStore.getAssociationById(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(true);
mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
/**
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
int userId = mAssociationStore.getAssociationById(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(false);
mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
/**
@@ -264,7 +277,8 @@ public class SystemDataTransferProcessor {
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- return Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
int userId = mAssociationStore.getAssociationById(associationId).getUserId();
List<SystemDataTransferRequest> requests =
mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
@@ -275,17 +289,22 @@ public class SystemDataTransferProcessor {
}
}
return null;
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
/**
* Remove perm sync request for the association.
*/
public void removePermissionSyncRequest(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
int userId = mAssociationStore.getAssociationById(associationId).getUserId();
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
private void onReceivePermissionRestore(byte[] message) {
@@ -299,12 +318,14 @@ public class SystemDataTransferProcessor {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
UserHandle user = mContext.getUser();
-
- Binder.withCleanCallingIdentity(() -> {
+ final long callingIdentityToken = Binder.clearCallingIdentity();
+ try {
// TODO: refactor to work with streams of data
mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
message, user);
- });
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentityToken);
+ }
}
private static void translateFutureToCallback(@NonNull Future<?> future,
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index c89ce11c169d..99466a966647 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -106,7 +106,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
checkBleState();
registerBluetoothStateBroadcastReceiver(context);
- mAssociationStore.registerLocalListener(this);
+ mAssociationStore.registerListener(this);
}
@MainThread
@@ -183,7 +183,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
// Collect MAC addresses from all associations.
final Set<String> macAddresses = new HashSet<>();
- for (AssociationInfo association : mAssociationStore.getActiveAssociations()) {
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
if (!association.isNotifyOnDeviceNearby()) continue;
// Beware that BT stack does not consider low-case MAC addresses valid, while
@@ -255,7 +255,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
+ mAssociationStore.getAssociationsByAddress(device.getAddress());
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
for (AssociationInfo association : associations) {
@@ -268,7 +268,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
+ mAssociationStore.getAssociationsByAddress(device.getAddress());
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
for (AssociationInfo association : associations) {
@@ -319,7 +319,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
Log.v(TAG, " > scanResult=" + result);
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
+ mAssociationStore.getAssociationsByAddress(device.getAddress());
Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray()));
}
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index cb363a7c9d7f..4da3f9bead4e 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -93,7 +93,7 @@ public class BluetoothCompanionDeviceConnectionListener
btAdapter.registerBluetoothConnectionCallback(
new HandlerExecutor(Handler.getMain()), /* callback */this);
- mAssociationStore.registerLocalListener(this);
+ mAssociationStore.registerListener(this);
}
/**
@@ -168,7 +168,7 @@ public class BluetoothCompanionDeviceConnectionListener
private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
int userId = UserHandle.myUserId();
final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
+ mAssociationStore.getAssociationsByAddress(device.getAddress());
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 7a1a83f53315..37bbb937d1b5 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -145,7 +145,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
Log.w(TAG, "BluetoothAdapter is NOT available.");
}
- mAssociationStore.registerLocalListener(this);
+ mAssociationStore.registerListener(this);
}
/**
@@ -481,7 +481,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
* BT connected and BLE presence and are not pending to report BLE lost.
*/
private boolean canStopBleScan() {
- for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
+ for (AssociationInfo ai : mAssociationStore.getAssociations()) {
int id = ai.getId();
synchronized (mBtDisconnectedDevices) {
if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index db15da2922cf..ee8b1065b42c 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -90,8 +90,6 @@ public class ObservableUuidStore {
* Remove the observable uuid.
*/
public void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
- Slog.i(TAG, "Removing uuid=[" + uuid.getUuid() + "] from store...");
-
List<ObservableUuid> cachedObservableUuids;
synchronized (mLock) {
@@ -110,7 +108,7 @@ public class ObservableUuidStore {
* Write the observable uuid.
*/
public void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
- Slog.i(TAG, "Writing uuid=[" + uuid.getUuid() + "] to store...");
+ Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
List<ObservableUuid> cachedObservableUuids;
synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
index 369a92504948..c75b1a57206e 100644
--- a/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
@@ -64,8 +64,8 @@ public final class DataStoreUtils {
* IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
* possible to synchronize reads and writes to the file using the returned object.
*
- * @param userId the userId to retrieve the storage file
- * @param fileName the storage file name
+ * @param userId the userId to retrieve the storage file
+ * @param fileName the storage file name
* @return an AtomicFile for the user
*/
@NonNull
diff --git a/services/companion/java/com/android/server/companion/utils/RolesUtils.java b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
index dd12e0406089..f798e218e8e0 100644
--- a/services/companion/java/com/android/server/companion/utils/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
@@ -93,8 +93,8 @@ public final class RolesUtils {
Slog.i(TAG, "Removing CDM role=" + deviceProfile
+ " for userId=" + userId + ", packageName=" + packageName);
-
- Binder.withCleanCallingIdentity(() ->
+ final long identity = Binder.clearCallingIdentity();
+ try {
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
@@ -103,9 +103,11 @@ public final class RolesUtils {
+ packageName + " from the list of " + deviceProfile
+ " holders.");
}
- })
- );
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
- private RolesUtils() {}
+ private RolesUtils() {};
}