Merge "Delay role revoke when the app is not visible to the user"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.companion;
|
||||
|
||||
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
|
||||
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;
|
||||
@@ -49,6 +50,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;
|
||||
@@ -93,6 +96,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;
|
||||
@@ -110,6 +114,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;
|
||||
@@ -133,6 +138,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;
|
||||
|
||||
@@ -160,12 +168,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));
|
||||
@@ -176,6 +212,9 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
mUserPersistenceHandler = new PersistUserStateHandler();
|
||||
mAssociationStore = new AssociationStoreImpl();
|
||||
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
|
||||
|
||||
mOnPackageVisibilityChangeListener =
|
||||
new OnPackageVisibilityChangeListener(mActivityManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,7 +256,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
|
||||
@@ -367,10 +432,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(
|
||||
@@ -438,13 +511,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,7 +789,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
|
||||
@@ -772,7 +849,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");
|
||||
}
|
||||
|
||||
@@ -832,8 +909,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);
|
||||
|
||||
@@ -845,6 +922,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;
|
||||
}
|
||||
|
||||
@@ -925,39 +1007,187 @@ 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);
|
||||
|
||||
// Remove all the system data transfer requests for the association.
|
||||
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
|
||||
|
||||
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());
|
||||
@@ -1175,4 +1405,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<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 #getStorageFileForUser(int)}
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
Reference in New Issue
Block a user