diff options
3 files changed, 406 insertions, 320 deletions
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java new file mode 100644 index 000000000000..de6382e316df --- /dev/null +++ b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java @@ -0,0 +1,369 @@ +/* + * 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; + +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.MetricUtils.logRemoveAssociation; +import static com.android.server.companion.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.datatransfer.SystemDataTransferRequestStore; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; + +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 AssociationStoreImpl mAssociationStore; + private final @NonNull PackageManagerInternal mPackageManagerInternal; + private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore; + private final @NonNull CompanionApplicationController mCompanionAppController; + 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<>(); + + AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service, + @NonNull AssociationStoreImpl associationStore, + @NonNull PackageManagerInternal packageManager, + @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor, + @NonNull CompanionApplicationController applicationController, + @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore) { + mService = service; + mContext = service.getContext(); + mActivityManager = mContext.getSystemService(ActivityManager.class); + mAssociationStore = associationStore; + mPackageManagerInternal = packageManager; + mOnPackageVisibilityChangeListener = + new OnPackageVisibilityChangeListener(mActivityManager); + mDevicePresenceMonitor = devicePresenceMonitor; + mCompanionAppController = applicationController; + mSystemDataTransferRequestStore = systemDataTransferRequestStore; + } + + // TODO: also revoke notification access + 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(); + + 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. + */ + 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); + return true; + } + + @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); + }); + } + + /** + * Set revoked flag for active association and add the revoked association and the uid into + * the caches. + * + * @see #mRevokedAssociationsPendingRoleHolderRemoval + * @see #mUidsPendingRoleHolderRemoval + * @see OnPackageVisibilityChangeListener + */ + 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(); + } + } + } + } + + /** + * 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(); + } + } + } + + /** + * @return a copy of the revoked associations set (safeguarding against + * {@code ConcurrentModificationException}-s). + */ + @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( + @UserIdInt int userId) { + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + // Return a copy. + return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); + } + } + + 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/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ba1f51baa624..09c77939eb7b 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -22,7 +22,6 @@ import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE; import static android.Manifest.permission.USE_COMPANION_TRANSPORTS; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; @@ -39,7 +38,6 @@ 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.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; -import static com.android.server.companion.MetricUtils.logRemoveAssociation; import static com.android.server.companion.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.PackageUtils.getPackageInfo; @@ -49,7 +47,6 @@ import static com.android.server.companion.PermissionsUtils.enforceCallerCanObse import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; -import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.DAYS; @@ -61,7 +58,6 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; -import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; @@ -149,7 +145,7 @@ public class CompanionDeviceManagerService extends SystemService { static final String TAG = "CDM_CompanionDeviceManagerService"; static final boolean DEBUG = false; - /** Range of Association IDs allocated for a user.*/ + /** Range of Association IDs allocated for a user. */ private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min @@ -162,8 +158,6 @@ public class CompanionDeviceManagerService extends SystemService { private static final int MAX_CN_LENGTH = 500; private final ActivityManager mActivityManager; - private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener; - private PersistentDataStore mPersistentStore; private final PersistUserStateHandler mUserPersistenceHandler; @@ -175,6 +169,7 @@ public class CompanionDeviceManagerService extends SystemService { private CompanionDevicePresenceMonitor mDevicePresenceMonitor; private CompanionApplicationController mCompanionAppController; private CompanionTransportManager mTransportManager; + private AssociationRevokeProcessor mAssociationRevokeProcessor; private final ActivityTaskManagerInternal mAtmInternal; private final ActivityManagerInternal mAmInternal; @@ -193,33 +188,6 @@ public class CompanionDeviceManagerService extends SystemService { @GuardedBy("mPreviouslyUsedIds") private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); - /** - * 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<>(); - private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = new RemoteCallbackList<>(); @@ -243,8 +211,6 @@ public class CompanionDeviceManagerService extends SystemService { mAssociationStore = new AssociationStoreImpl(); mSystemDataTransferRequestStore = new SystemDataTransferRequestStore(); - mOnPackageVisibilityChangeListener = - new OnPackageVisibilityChangeListener(mActivityManager); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mObservableUuidStore = new ObservableUuidStore(); } @@ -276,6 +242,9 @@ public class CompanionDeviceManagerService extends SystemService { mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mPackageManagerInternal, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); + mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore, + mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController, + mSystemDataTransferRequestStore); // TODO(b/279663946): move context sync to a dedicated system service mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager); @@ -307,12 +276,13 @@ public class CompanionDeviceManagerService extends SystemService { mBackupRestoreProcessor.addToPendingAppInstall(association); } else if (!association.isRevoked()) { activeAssociations.add(association); - } else if (maybeRemoveRoleHolderForAssociation(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 { - addToPendingRoleHolderRemoval(association); + mAssociationRevokeProcessor.addToPendingRoleHolderRemoval(association); } } @@ -374,7 +344,7 @@ public class CompanionDeviceManagerService extends SystemService { final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); - for (AssociationInfo ai: + for (AssociationInfo ai : mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) { Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected"); mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId()); @@ -495,7 +465,7 @@ public class CompanionDeviceManagerService extends SystemService { final String packageName = uuid.getPackageName(); final int userId = uuid.getUserId(); - switch(event) { + switch (event) { case EVENT_BT_CONNECTED: if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { mCompanionAppController.bindCompanionApplication( @@ -544,8 +514,8 @@ public class CompanionDeviceManagerService extends SystemService { /** * @return whether the package should be bound (i.e. at least one of the devices associated with - * the package is currently present OR the UUID to be observed by this package is - * currently present). + * the package is currently present OR the UUID to be observed by this package is + * currently present). */ private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { final List<AssociationInfo> packageAssociations = @@ -599,7 +569,8 @@ public class CompanionDeviceManagerService extends SystemService { allAssociations = new ArrayList<>( mAssociationStore.getAssociationsForUser(userId)); // ... and add the revoked (removed) association, that are yet to be permanently removed. - allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId)); + allAssociations.addAll( + mAssociationRevokeProcessor.getPendingRoleHolderRemovalAssociationsForUser(userId)); // ... and add the restored associations that are pending missing package installation. allAssociations.addAll(mBackupRestoreProcessor .getAssociationsPendingAppInstallForUser(userId)); @@ -654,7 +625,7 @@ public class CompanionDeviceManagerService extends SystemService { } // Clear role holders for (AssociationInfo association : associationsForPackage) { - maybeRemoveRoleHolderForAssociation(association); + mAssociationRevokeProcessor.maybeRemoveRoleHolderForAssociation(association); } // Clear the uuids to be observed. for (ObservableUuid uuid : uuidsTobeObserved) { @@ -712,7 +683,7 @@ public class CompanionDeviceManagerService extends SystemService { final int id = association.getId(); Slog.i(TAG, "Removing inactive self-managed association id=" + id); - disassociateInternal(id); + mAssociationRevokeProcessor.disassociateInternal(id); } } @@ -857,7 +828,7 @@ public class CompanionDeviceManagerService extends SystemService { final AssociationInfo association = getAssociationWithCallerChecks(userId, packageName, deviceMacAddress); - disassociateInternal(association.getId()); + mAssociationRevokeProcessor.disassociateInternal(association.getId()); } @Override @@ -866,7 +837,7 @@ public class CompanionDeviceManagerService extends SystemService { final AssociationInfo association = getAssociationWithCallerChecks(associationId); - disassociateInternal(association.getId()); + mAssociationRevokeProcessor.disassociateInternal(association.getId()); } @Override @@ -902,9 +873,9 @@ public class CompanionDeviceManagerService extends SystemService { } /** - * @deprecated Use - * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead. - */ + * @deprecated Use + * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead. + */ @Deprecated @Override public boolean hasNotificationAccess(ComponentName component) throws RemoteException { @@ -1300,7 +1271,7 @@ public class CompanionDeviceManagerService extends SystemService { return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore, mDevicePresenceMonitor, mTransportManager, mSystemDataTransferProcessor, mAssociationRequestsProcessor, - mBackupRestoreProcessor) + mBackupRestoreProcessor, mAssociationRevokeProcessor) .exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } @@ -1381,7 +1352,7 @@ public class CompanionDeviceManagerService extends SystemService { // another association by the time when it is activated from the package installation. final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor .getAssociationsPendingAppInstallForUser(userId); - for (AssociationInfo it: pendingAssociations) { + for (AssociationInfo it : pendingAssociations) { usedIds.put(it.getId(), true); } @@ -1407,198 +1378,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - // TODO: also revoke notification access - 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(); - - 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. - */ - - private 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(getContext(), association); - return true; - } - - 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); - }); - } - - /** - * Set revoked flag for active association and add the revoked association and the uid into - * the caches. - * - * @see #mRevokedAssociationsPendingRoleHolderRemoval - * @see #mUidsPendingRoleHolderRemoval - * @see OnPackageVisibilityChangeListener - */ - private 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(); - } - } - } - } - - /** - * 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(); - } - } - } - - /** - * @return a copy of the revoked associations set (safeguarding against - * {@code ConcurrentModificationException}-s). - */ - private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( - @UserIdInt int userId) { - synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { - // Return a copy. - return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); - } - } - - private String getPackageNameByUid(int uid) { - synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { - return mUidsPendingRoleHolderRemoval.get(uid); - } - } - void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { final PackageInfo packageInfo = getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); @@ -1704,11 +1483,11 @@ public class CompanionDeviceManagerService extends SystemService { private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = new AssociationStore.OnChangeListener() { - @Override - public void onAssociationChanged(int changeType, AssociationInfo association) { - onAssociationChangedInternal(changeType, association); - } - }; + @Override + public void onAssociationChanged(int changeType, AssociationInfo association) { + onAssociationChangedInternal(changeType, association); + } + }; private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = new CompanionDevicePresenceMonitor.Callback() { @@ -1731,7 +1510,7 @@ public class CompanionDeviceManagerService extends SystemService { public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) { onDevicePresenceEventByUuidInternal(uuid, event); } - }; + }; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override @@ -1887,73 +1666,8 @@ public class CompanionDeviceManagerService extends SystemService { } } - /** - * 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 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; - } - - void startListening() { - Binder.withCleanCallingIdentity( - () -> mAm.addOnUidImportanceListener( - /* listener */ OnPackageVisibilityChangeListener.this, - RunningAppProcessInfo.IMPORTANCE_VISIBLE)); - } - - void stopListening() { - Binder.withCleanCallingIdentity( - () -> mAm.removeOnUidImportanceListener( - /* listener */ OnPackageVisibilityChangeListener.this)); - } - - @Override - public void onUidImportance(int uid, int importance) { - if (importance <= 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) { - mUserPersistenceHandler.postPersistUserState(userId); - } - } + void postPersistUserState(@UserIdInt int userId) { + mUserPersistenceHandler.postPersistUserState(userId); } static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5663434e2b6d..de4f2b60170f 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -45,6 +45,7 @@ class CompanionDeviceShellCommand extends ShellCommand { private static final String TAG = "CDM_CompanionDeviceShellCommand"; private final CompanionDeviceManagerService mService; + private final AssociationRevokeProcessor mRevokeProcessor; private final AssociationStoreImpl mAssociationStore; private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; private final CompanionTransportManager mTransportManager; @@ -59,7 +60,8 @@ class CompanionDeviceShellCommand extends ShellCommand { CompanionTransportManager transportManager, SystemDataTransferProcessor systemDataTransferProcessor, AssociationRequestsProcessor associationRequestsProcessor, - BackupRestoreProcessor backupRestoreProcessor) { + BackupRestoreProcessor backupRestoreProcessor, + AssociationRevokeProcessor revokeProcessor) { mService = service; mAssociationStore = associationStore; mDevicePresenceMonitor = devicePresenceMonitor; @@ -67,6 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand { mSystemDataTransferProcessor = systemDataTransferProcessor; mAssociationRequestsProcessor = associationRequestsProcessor; mBackupRestoreProcessor = backupRestoreProcessor; + mRevokeProcessor = revokeProcessor; } @Override @@ -126,7 +129,7 @@ class CompanionDeviceShellCommand extends ShellCommand { final AssociationInfo association = mService.getAssociationWithCallerChecks(userId, packageName, address); if (association != null) { - mService.disassociateInternal(association.getId()); + mRevokeProcessor.disassociateInternal(association.getId()); } } break; @@ -138,7 +141,7 @@ class CompanionDeviceShellCommand extends ShellCommand { mAssociationStore.getAssociationsForPackage(userId, packageName); for (AssociationInfo association : userAssociations) { if (sanitizeWithCallerChecks(mService.getContext(), association) != null) { - mService.disassociateInternal(association.getId()); + mRevokeProcessor.disassociateInternal(association.getId()); } } } |