Merge "Delay role revoke when the app is not visible to the user" into tm-qpr-dev

This commit is contained in:
Evan Chen
2022-06-24 04:25:45 +00:00
committed by Android (Google) Code Review
5 changed files with 415 additions and 39 deletions

View File

@@ -55,6 +55,14 @@ public final class AssociationInfo implements Parcelable {
private final boolean mSelfManaged; private final boolean mSelfManaged;
private final boolean mNotifyOnDeviceNearby; private final boolean mNotifyOnDeviceNearby;
/**
* Indicates that the association has been revoked (removed), but we keep the association
* record for final clean up (e.g. removing the app from the list of the role holders).
*
* @see CompanionDeviceManager#disassociate(int)
*/
private final boolean mRevoked;
private final long mTimeApprovedMs; private final long mTimeApprovedMs;
/** /**
* A long value indicates the last time connected reported by selfManaged devices * A long value indicates the last time connected reported by selfManaged devices
@@ -71,7 +79,7 @@ public final class AssociationInfo implements Parcelable {
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
long timeApprovedMs, long lastTimeConnectedMs) { boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) { if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0"); throw new IllegalArgumentException("Association ID should be greater than 0");
} }
@@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged; mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby; mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mRevoked = revoked;
mTimeApprovedMs = timeApprovedMs; mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs; mLastTimeConnectedMs = lastTimeConnectedMs;
} }
@@ -175,6 +184,14 @@ public final class AssociationInfo implements Parcelable {
return mUserId == userId && Objects.equals(mPackageName, packageName); return mUserId == userId && Objects.equals(mPackageName, packageName);
} }
/**
* @return if the association has been revoked (removed).
* @hide
*/
public boolean isRevoked() {
return mRevoked;
}
/** /**
* @return the last time self reported disconnected for selfManaged only. * @return the last time self reported disconnected for selfManaged only.
* @hide * @hide
@@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mDeviceProfile='" + mDeviceProfile + '\'' + ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged + ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + ( + ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE mLastTimeConnectedMs == Long.MAX_VALUE
@@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable {
&& mUserId == that.mUserId && mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged && mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mRevoked == that.mRevoked
&& mTimeApprovedMs == that.mTimeApprovedMs && mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs && mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName) && Objects.equals(mPackageName, that.mPackageName)
@@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs, mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
mLastTimeConnectedMs); mLastTimeConnectedMs);
} }
@@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged); dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby); dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeBoolean(mRevoked);
dest.writeLong(mTimeApprovedMs); dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs); dest.writeLong(mLastTimeConnectedMs);
} }
@@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean(); mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean(); mNotifyOnDeviceNearby = in.readBoolean();
mRevoked = in.readBoolean();
mTimeApprovedMs = in.readLong(); mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong(); mLastTimeConnectedMs = in.readLong();
} }
@@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable {
@NonNull @NonNull
private final AssociationInfo mOriginalInfo; private final AssociationInfo mOriginalInfo;
private boolean mNotifyOnDeviceNearby; private boolean mNotifyOnDeviceNearby;
private boolean mRevoked;
private long mLastTimeConnectedMs; private long mLastTimeConnectedMs;
private Builder(@NonNull AssociationInfo info) { private Builder(@NonNull AssociationInfo info) {
mOriginalInfo = info; mOriginalInfo = info;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
mRevoked = info.mRevoked;
mLastTimeConnectedMs = info.mLastTimeConnectedMs; mLastTimeConnectedMs = info.mLastTimeConnectedMs;
} }
@@ -387,6 +410,17 @@ public final class AssociationInfo implements Parcelable {
return this; return this;
} }
/**
* Should only be used by the CompanionDeviceManagerService.
* @hide
*/
@Override
@NonNull
public Builder setRevoked(boolean revoked) {
mRevoked = revoked;
return this;
}
/** /**
* @hide * @hide
*/ */
@@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable {
mOriginalInfo.mDeviceProfile, mOriginalInfo.mDeviceProfile,
mOriginalInfo.mSelfManaged, mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby, mNotifyOnDeviceNearby,
mRevoked,
mOriginalInfo.mTimeApprovedMs, mOriginalInfo.mTimeApprovedMs,
mLastTimeConnectedMs mLastTimeConnectedMs
); );
@@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable {
*/ */
@NonNull @NonNull
Builder setLastTimeConnected(long lastTimeConnectedMs); Builder setLastTimeConnected(long lastTimeConnectedMs);
/**
* Should only be used by the CompanionDeviceManagerService.
* @hide
*/
@NonNull
Builder setRevoked(boolean revoked);
} }
} }

View File

@@ -73,6 +73,9 @@ class AssociationStoreImpl implements AssociationStore {
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
void addAssociation(@NonNull AssociationInfo association) { void addAssociation(@NonNull AssociationInfo association) {
// Validity check first.
checkNotRevoked(association);
final int id = association.getId(); final int id = association.getId();
if (DEBUG) { if (DEBUG) {
@@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore {
} }
void updateAssociation(@NonNull AssociationInfo updated) { void updateAssociation(@NonNull AssociationInfo updated) {
// Validity check first.
checkNotRevoked(updated);
final int id = updated.getId(); final int id = updated.getId();
if (DEBUG) { if (DEBUG) {
@@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore {
} }
void setAssociations(Collection<AssociationInfo> allAssociations) { void setAssociations(Collection<AssociationInfo> allAssociations) {
// Validity check first.
allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
if (DEBUG) { if (DEBUG) {
Log.i(TAG, "setAssociations() n=" + allAssociations.size()); Log.i(TAG, "setAssociations() n=" + allAssociations.size());
final StringJoiner stringJoiner = new StringJoiner(", "); final StringJoiner stringJoiner = new StringJoiner(", ");
@@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore {
mAddressMap.clear(); mAddressMap.clear();
mCachedPerUser.clear(); mCachedPerUser.clear();
} }
private static void checkNotRevoked(@NonNull AssociationInfo association) {
if (association.isRevoked()) {
throw new IllegalArgumentException(
"Revoked (removed) associations MUST NOT appear in the AssociationStore");
}
}
} }

View File

@@ -18,6 +18,7 @@
package com.android.server.companion; package com.android.server.companion;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID; import static android.os.Process.SYSTEM_UID;
@@ -48,6 +49,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -91,6 +94,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor; import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.PerUser;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract; import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread; import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
@@ -104,6 +108,7 @@ import com.android.server.pm.UserManagerInternal;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -127,6 +132,9 @@ public class CompanionDeviceManagerService extends SystemService {
private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private final ActivityManager mActivityManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
private PersistentDataStore mPersistentStore; private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler; private final PersistUserStateHandler mUserPersistenceHandler;
@@ -150,12 +158,40 @@ public class CompanionDeviceManagerService extends SystemService {
@GuardedBy("mPreviouslyUsedIds") @GuardedBy("mPreviouslyUsedIds")
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); 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 = private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>(); new RemoteCallbackList<>();
public CompanionDeviceManagerService(Context context) { public CompanionDeviceManagerService(Context context) {
super(context); super(context);
mActivityManager = context.getSystemService(ActivityManager.class);
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface( mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE)); ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -165,6 +201,9 @@ public class CompanionDeviceManagerService extends SystemService {
mUserPersistenceHandler = new PersistUserStateHandler(); mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl(); mAssociationStore = new AssociationStoreImpl();
mOnPackageVisibilityChangeListener =
new OnPackageVisibilityChangeListener(mActivityManager);
} }
@Override @Override
@@ -201,7 +240,33 @@ public class CompanionDeviceManagerService extends SystemService {
mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
} }
mAssociationStore.setAssociations(allAssociations); 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.isRevoked()) {
activeAssociations.add(association);
} else if (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);
}
}
mAssociationStore.setAssociations(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 @Override
@@ -351,10 +416,18 @@ public class CompanionDeviceManagerService extends SystemService {
} }
private void persistStateForUser(@UserIdInt int userId) { private void persistStateForUser(@UserIdInt int userId) {
final List<AssociationInfo> updatedAssociations = // We want to store both active associations and the revoked (removed) association that we
mAssociationStore.getAssociationsForUser(userId); // 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(getPendingRoleHolderRemovalAssociationsForUser(userId));
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
} }
private void notifyListeners( private void notifyListeners(
@@ -422,13 +495,17 @@ public class CompanionDeviceManagerService extends SystemService {
removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
} }
for (AssociationInfo ai : mAssociationStore.getAssociations()) { for (AssociationInfo association : mAssociationStore.getAssociations()) {
if (!ai.isSelfManaged()) continue; if (!association.isSelfManaged()) continue;
final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow;
if (isInactive) { final boolean isInactive =
Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId()); currentTime - association.getLastTimeConnectedMs() >= removalWindow;
disassociateInternal(ai.getId()); if (!isInactive) continue;
}
final int id = association.getId();
Slog.i(TAG, "Removing inactive self-managed association id=" + id);
disassociateInternal(id);
} }
} }
@@ -788,8 +865,8 @@ public class CompanionDeviceManagerService extends SystemService {
final long timestamp = System.currentTimeMillis(); final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName, final AssociationInfo association = new AssociationInfo(id, userId, packageName,
macAddress, displayName, deviceProfile, selfManaged, false, timestamp, macAddress, displayName, deviceProfile, selfManaged,
Long.MAX_VALUE); /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association); Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association); mAssociationStore.addAssociation(association);
@@ -801,6 +878,11 @@ public class CompanionDeviceManagerService extends SystemService {
updateSpecialAccessPermissionForAssociatedPackage(association); updateSpecialAccessPermissionForAssociatedPackage(association);
logCreateAssociation(deviceProfile); logCreateAssociation(deviceProfile);
// 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.
return association; return association;
} }
@@ -881,36 +963,184 @@ public class CompanionDeviceManagerService extends SystemService {
final String packageName = association.getPackageName(); final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile(); 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); final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
// Removing the association. // Removing the association.
mAssociationStore.removeAssociation(associationId); 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); logRemoveAssociation(deviceProfile);
final List<AssociationInfo> otherAssociations =
mAssociationStore.getAssociationsForPackage(userId, packageName);
// Check if the package is associated with other devices with the same profile.
// If not: take away the role.
if (deviceProfile != null) {
final boolean shouldKeepTheRole = any(otherAssociations,
it -> deviceProfile.equals(it.getDeviceProfile()));
if (!shouldKeepTheRole) {
Binder.withCleanCallingIdentity(() ->
removeRoleHolderForAssociation(getContext(), association));
}
}
if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
// The device was connected and the app was notified: check if we need to unbind the app // The device was connected and the app was notified: check if we need to unbind the app
// now. // now.
final boolean shouldStayBound = any(otherAssociations, final boolean shouldStayBound = any(
mAssociationStore.getAssociationsForPackage(userId, packageName),
it -> it.isNotifyOnDeviceNearby() it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId())); && mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (shouldStayBound) return; if (shouldStayBound) return;
mCompanionAppController.unbindCompanionApplication(userId, packageName); 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;
}
// 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 = 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 form the cache and also remove the uid form 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 form 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);
}
}
private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo = final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -1128,4 +1358,80 @@ public class CompanionDeviceManagerService extends SystemService {
persistStateForUser(userId); persistStateForUser(userId);
} }
} }
/**
* 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);
}
}
}
private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
}
}
} }

View File

@@ -103,7 +103,7 @@ import java.util.concurrent.ConcurrentMap;
* Since Android T the data is stored to "companion_device_manager.xml" file in * Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}. * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
* *
* See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()} * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
* *
* <p> * <p>
* Since Android T the data is stored using the v1 schema. * Since Android T the data is stored using the v1 schema.
@@ -120,7 +120,7 @@ import java.util.concurrent.ConcurrentMap;
* <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()} * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
* </ul> * </ul>
* *
* The following snippet is a sample of a file that is using v0 schema. * The following snippet is a sample of a file that is using v1 schema.
* <pre>{@code * <pre>{@code
* <state persistence-version="1"> * <state persistence-version="1">
* <associations> * <associations>
@@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap;
* mac_address="AA:BB:CC:DD:EE:00" * mac_address="AA:BB:CC:DD:EE:00"
* self_managed="false" * self_managed="false"
* notify_device_nearby="false" * notify_device_nearby="false"
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634389553216"/> * time_approved="1634389553216"/>
* *
* <association * <association
@@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap;
* display_name="Jhon's Chromebook" * display_name="Jhon's Chromebook"
* self_managed="true" * self_managed="true"
* notify_device_nearby="false" * notify_device_nearby="false"
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634641160229"/> * time_approved="1634641160229"/>
* </associations> * </associations>
* *
@@ -178,6 +182,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_PROFILE = "profile"; private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_SELF_MANAGED = "self_managed"; private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby"; private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_ATTR_REVOKED = "revoked";
private static final String XML_ATTR_TIME_APPROVED = "time_approved"; private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected"; private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
@@ -415,7 +420,8 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage, out.add(new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile, MacAddress.fromString(deviceAddress), null, profile,
/* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE)); /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
Long.MAX_VALUE));
} }
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser, private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,13 +450,14 @@ final class PersistentDataStore {
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME); final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED); final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY); final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute( final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE); parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId, final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved, appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
lastTimeConnected); timeApproved, lastTimeConnected);
if (associationInfo != null) { if (associationInfo != null) {
out.add(associationInfo); out.add(associationInfo);
} }
@@ -503,6 +510,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged()); writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute( writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby()); serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
writeBooleanAttribute(
serializer, XML_ATTR_REVOKED, a.isRevoked());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs()); writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute( writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs()); serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -544,11 +553,12 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId, private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress, @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged, @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
boolean notify, long timeApproved, long lastTimeConnected) { boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null; AssociationInfo associationInfo = null;
try { try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected); displayName, profile, selfManaged, notify, revoked, timeApproved,
lastTimeConnected);
} catch (Exception e) { } catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
} }

View File

@@ -85,6 +85,8 @@ final class RolesUtils {
final int userId = associationInfo.getUserId(); final int userId = associationInfo.getUserId();
final UserHandle userHandle = UserHandle.of(userId); final UserHandle userHandle = UserHandle.of(userId);
Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
+ ", package=u" + userId + "\\" + packageName);
roleManager.removeRoleHolderAsUser(deviceProfile, packageName, roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> { success -> {