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 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;
/**
* 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,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
long timeApprovedMs, long lastTimeConnectedMs) {
boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mRevoked = revoked;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
}
@@ -175,6 +184,14 @@ public final class AssociationInfo implements Parcelable {
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.
* @hide
@@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable {
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mRevoked == that.mRevoked
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs,
mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
mLastTimeConnectedMs);
}
@@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeBoolean(mRevoked);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
}
@@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
mRevoked = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
}
@@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable {
@NonNull
private final AssociationInfo mOriginalInfo;
private boolean mNotifyOnDeviceNearby;
private boolean mRevoked;
private long mLastTimeConnectedMs;
private Builder(@NonNull AssociationInfo info) {
mOriginalInfo = info;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
mRevoked = info.mRevoked;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
}
@@ -387,6 +410,17 @@ public final class AssociationInfo implements Parcelable {
return this;
}
/**
* Should only be used by the CompanionDeviceManagerService.
* @hide
*/
@Override
@NonNull
public Builder setRevoked(boolean revoked) {
mRevoked = revoked;
return this;
}
/**
* @hide
*/
@@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable {
mOriginalInfo.mDeviceProfile,
mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby,
mRevoked,
mOriginalInfo.mTimeApprovedMs,
mLastTimeConnectedMs
);
@@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable {
*/
@NonNull
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<>();
void addAssociation(@NonNull AssociationInfo association) {
// Validity check first.
checkNotRevoked(association);
final int id = association.getId();
if (DEBUG) {
@@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void updateAssociation(@NonNull AssociationInfo updated) {
// Validity check first.
checkNotRevoked(updated);
final int id = updated.getId();
if (DEBUG) {
@@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void setAssociations(Collection<AssociationInfo> allAssociations) {
// Validity check first.
allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
if (DEBUG) {
Log.i(TAG, "setAssociations() n=" + allAssociations.size());
final StringJoiner stringJoiner = new StringJoiner(", ");
@@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore {
mAddressMap.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;
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.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -48,6 +49,8 @@ import android.annotation.NonNull;
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;
@@ -91,6 +94,7 @@ 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;
@@ -104,6 +108,7 @@ import com.android.server.pm.UserManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
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 final ActivityManager mActivityManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler;
@@ -150,12 +158,40 @@ 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<>();
public CompanionDeviceManagerService(Context context) {
super(context);
mActivityManager = context.getSystemService(ActivityManager.class);
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -165,6 +201,9 @@ public class CompanionDeviceManagerService extends SystemService {
mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl();
mOnPackageVisibilityChangeListener =
new OnPackageVisibilityChangeListener(mActivityManager);
}
@Override
@@ -201,7 +240,33 @@ public class CompanionDeviceManagerService extends SystemService {
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
@@ -351,10 +416,18 @@ public class CompanionDeviceManagerService extends SystemService {
}
private void persistStateForUser(@UserIdInt int userId) {
final List<AssociationInfo> updatedAssociations =
mAssociationStore.getAssociationsForUser(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(getPendingRoleHolderRemovalAssociationsForUser(userId));
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -422,13 +495,17 @@ public class CompanionDeviceManagerService extends SystemService {
removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
}
for (AssociationInfo ai : mAssociationStore.getAssociations()) {
if (!ai.isSelfManaged()) continue;
final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow;
if (isInactive) {
Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId());
disassociateInternal(ai.getId());
}
for (AssociationInfo association : mAssociationStore.getAssociations()) {
if (!association.isSelfManaged()) continue;
final boolean isInactive =
currentTime - association.getLastTimeConnectedMs() >= removalWindow;
if (!isInactive) continue;
final int id = association.getId();
Slog.i(TAG, "Removing inactive self-managed association id=" + id);
disassociateInternal(id);
}
}
@@ -668,7 +745,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOr(userId, packageName);
AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, deviceAddress);
userId, packageName, deviceAddress);
if (association == null) {
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
@@ -728,7 +805,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
checkState(!ArrayUtils.isEmpty(
mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
"App must have an association before calling this API");
}
@@ -788,8 +865,8 @@ public class CompanionDeviceManagerService extends SystemService {
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
Long.MAX_VALUE);
macAddress, displayName, deviceProfile, selfManaged,
/* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
@@ -801,6 +878,11 @@ public class CompanionDeviceManagerService extends SystemService {
updateSpecialAccessPermissionForAssociatedPackage(association);
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;
}
@@ -881,36 +963,184 @@ public class CompanionDeviceManagerService extends SystemService {
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);
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;
// The device was connected and the app was notified: check if we need to unbind the app
// now.
final boolean shouldStayBound = any(otherAssociations,
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;
}
// 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) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -1128,4 +1358,80 @@ public class CompanionDeviceManagerService extends SystemService {
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
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
* See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()}
* See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
*
* <p>
* 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()}
* </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
* <state persistence-version="1">
* <associations>
@@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap;
* mac_address="AA:BB:CC:DD:EE:00"
* self_managed="false"
* notify_device_nearby="false"
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634389553216"/>
*
* <association
@@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap;
* display_name="Jhon's Chromebook"
* self_managed="true"
* notify_device_nearby="false"
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634641160229"/>
* </associations>
*
@@ -178,6 +182,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_PROFILE = "profile";
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_REVOKED = "revoked";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
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,
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,
@@ -444,13 +450,14 @@ final class PersistentDataStore {
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
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 lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved,
lastTimeConnected);
appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
timeApproved, lastTimeConnected);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -503,6 +510,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
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_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -544,11 +553,12 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@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;
try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
displayName, profile, selfManaged, notify, revoked, timeApproved,
lastTimeConnected);
} catch (Exception 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 UserHandle userHandle = UserHandle.of(userId);
Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
+ ", package=u" + userId + "\\" + packageName);
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {