Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57a1d062 authored by Guojing Yuan's avatar Guojing Yuan
Browse files

[CDM] Add CompanionExemptionProcessor

1. Auto revoke exemption now checks if there's any association for the package.
2. Power saver exemption now checks if there's any connected device for the
   package.
3. TODO: CompanionAppBinder.onPackageChanged shouldn't invalidate the
   whole user registry, it should rather remove the package only. I will
   do it in a separate CL.

Bug: 373957005
Test: manually tested 3 exemptions
Flag: EXEMPT bugfix
Change-Id: I8e3a6099c3e09b0bdb22844e9a57c415f6179f46
parent 69a135b2
Loading
Loading
Loading
Loading
+37 −179
Original line number Diff line number Diff line
@@ -31,16 +31,13 @@ import static android.os.UserHandle.getCallingUserId;

import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;

import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MINUTES;

import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -69,31 +66,22 @@ import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.flags.Flags;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;

import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
@@ -114,35 +102,27 @@ import com.android.server.companion.devicepresence.DevicePresenceProcessor;
import com.android.server.companion.devicepresence.ObservableUuid;
import com.android.server.companion.devicepresence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;

import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService {
    private static final String TAG = "CDM_CompanionDeviceManagerService";

    private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min

    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
    private static final int MAX_CN_LENGTH = 500;

    private final ActivityTaskManagerInternal mAtmInternal;
    private final ActivityManagerInternal mAmInternal;
    private final IAppOpsService mAppOpsManager;
    private final PowerExemptionManager mPowerExemptionManager;
    private final PackageManagerInternal mPackageManagerInternal;

    private final AssociationStore mAssociationStore;
    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
    private final ObservableUuidStore mObservableUuidStore;

    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
    private final SystemDataTransferProcessor mSystemDataTransferProcessor;
    private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -156,12 +136,15 @@ public class CompanionDeviceManagerService extends SystemService {
        super(context);

        final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
        mAppOpsManager = IAppOpsService.Stub.asInterface(
                ServiceManager.getService(Context.APP_OPS_SERVICE));
        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
        final PowerExemptionManager powerExemptionManager = context.getSystemService(
                PowerExemptionManager.class);
        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
        final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
                ActivityTaskManagerInternal.class);
        final ActivityManagerInternal amInternal = LocalServices.getService(
                ActivityManagerInternal.class);
        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        final UserManager userManager = context.getSystemService(UserManager.class);
        final PowerManagerInternal powerManagerInternal = LocalServices.getService(
                PowerManagerInternal.class);
@@ -173,25 +156,29 @@ public class CompanionDeviceManagerService extends SystemService {

        // Init processors
        mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
                mPackageManagerInternal, mAssociationStore);
        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
                packageManagerInternal, mAssociationStore);
        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
                mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                mAssociationRequestsProcessor);

        mCompanionAppBinder = new CompanionAppBinder(context);

        mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
                powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
                amInternal, mAssociationStore);

        mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
                powerManagerInternal);
                powerManagerInternal, mCompanionExemptionProcessor);

        mTransportManager = new CompanionTransportManager(context, mAssociationStore);

        mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
                mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
                mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
                mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);

        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                mPackageManagerInternal, mAssociationStore,
                packageManagerInternal, mAssociationStore,
                mSystemDataTransferRequestStore, mTransportManager);

        // TODO(b/279663946): move context sync to a dedicated system service
@@ -202,7 +189,6 @@ public class CompanionDeviceManagerService extends SystemService {
    public void onStart() {
        // Init association stores
        mAssociationStore.refreshCache();
        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);

        // Init UUID store
        mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -240,11 +226,11 @@ public class CompanionDeviceManagerService extends SystemService {

        if (associations.isEmpty()) return;

        updateAtm(userId, associations);
        mCompanionExemptionProcessor.updateAtm(userId, associations);

        BackgroundThread.getHandler().sendMessageDelayed(
                obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
                MINUTES.toMillis(10));
        try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
            executor.execute(mCompanionExemptionProcessor::updateAutoRevokeExemptions);
        }
    }

    @Override
@@ -262,30 +248,30 @@ public class CompanionDeviceManagerService extends SystemService {
        if (!associationsForPackage.isEmpty()) {
            Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
                    + packageName + "]. Cleaning up CDM data...");
        }

            for (AssociationInfo association : associationsForPackage) {
                mDisassociationProcessor.disassociate(association.getId());
            }

            mCompanionAppBinder.onPackagesChanged(userId, packageName);
        }

        // Clear observable UUIDs for the package.
        final List<ObservableUuid> uuidsTobeObserved =
                mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
        for (ObservableUuid uuid : uuidsTobeObserved) {
            mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
        }

        mCompanionAppBinder.onPackagesChanged(userId);
    }

    private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
        final List<AssociationInfo> associationsForPackage =
        final List<AssociationInfo> associations =
                mAssociationStore.getAssociationsByPackage(userId, packageName);
        for (AssociationInfo association : associationsForPackage) {
            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
                    association.getPackageName());
        }
        if (!associations.isEmpty()) {
            mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);

        mCompanionAppBinder.onPackagesChanged(userId);
            mCompanionAppBinder.onPackagesChanged(userId, packageName);
        }
    }

    private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -765,130 +751,6 @@ public class CompanionDeviceManagerService extends SystemService {
        }
    }

    /**
     * Update special access for the association's package
     */
    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
        final PackageInfo packageInfo =
                getPackageInfo(getContext(), userId, packageName);

        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
    }

    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
        if (packageInfo == null) {
            return;
        }

        if (containsEither(packageInfo.requestedPermissions,
                android.Manifest.permission.RUN_IN_BACKGROUND,
                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
        } else {
            try {
                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
            } catch (UnsupportedOperationException e) {
                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
                        + " whitelist. It might due to the package is whitelisted by the system.");
            }
        }

        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
        try {
            if (containsEither(packageInfo.requestedPermissions,
                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
                networkPolicyManager.addUidPolicy(
                        packageInfo.applicationInfo.uid,
                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
            } else {
                networkPolicyManager.removeUidPolicy(
                        packageInfo.applicationInfo.uid,
                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
            }
        } catch (IllegalArgumentException e) {
            Slog.e(TAG, e.getMessage());
        }

        exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
    }

    private void exemptFromAutoRevoke(String packageName, int uid) {
        try {
            mAppOpsManager.setMode(
                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
                    uid,
                    packageName,
                    AppOpsManager.MODE_IGNORED);
        } catch (RemoteException e) {
            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
        }
    }

    private void updateAtm(int userId, List<AssociationInfo> associations) {
        final Set<Integer> companionAppUids = new ArraySet<>();
        for (AssociationInfo association : associations) {
            final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
                    0, userId);
            if (uid >= 0) {
                companionAppUids.add(uid);
            }
        }
        if (mAtmInternal != null) {
            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
        }
        if (mAmInternal != null) {
            // Make a copy of the set and send it to ActivityManager.
            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
        }
    }

    private void maybeGrantAutoRevokeExemptions() {
        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");

        PackageManager pm = getContext().getPackageManager();
        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
            SharedPreferences pref = getContext().getSharedPreferences(
                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
                    Context.MODE_PRIVATE);
            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
                continue;
            }

            try {
                final List<AssociationInfo> associations =
                        mAssociationStore.getActiveAssociationsByUser(userId);
                for (AssociationInfo a : associations) {
                    try {
                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
                        exemptFromAutoRevoke(a.getPackageName(), uid);
                    } catch (PackageManager.NameNotFoundException e) {
                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
                    }
                }
            } finally {
                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
            }
        }
    }

    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
            new AssociationStore.OnChangeListener() {
                @Override
                public void onAssociationChanged(int changeType, AssociationInfo association) {
                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
                            + "], association=[" + association);

                    final int userId = association.getUserId();
                    final List<AssociationInfo> updatedAssociations =
                            mAssociationStore.getActiveAssociationsByUser(userId);

                    updateAtm(userId, updatedAssociations);
                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
                            association.getPackageName());
                }
            };

    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
        @Override
        public void onPackageRemoved(String packageName, int uid) {
@@ -911,10 +773,6 @@ public class CompanionDeviceManagerService extends SystemService {
        }
    };

    private static <T> boolean containsEither(T[] array, T a, T b) {
        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
    }

    private class LocalService implements CompanionDeviceManagerServiceInternal {

        @Override
+219 −0

File added.

Preview size limit exceeded, changes collapsed.

+4 −1
Original line number Diff line number Diff line
@@ -95,7 +95,10 @@ public class CompanionAppBinder {
    /**
     * On package changed.
     */
    public void onPackagesChanged(@UserIdInt int userId) {
    public void onPackagesChanged(@UserIdInt int userId, String packageName) {
        // TODO: We shouldn't need to clean up the whole user registry. We only need to remove the
        //       package. I will do it in a separate change since it's not appropriate to use
        //       PerUser anymore.
        mCompanionServicesRegister.invalidate(userId);
    }

+17 −10
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.util.SparseBooleanArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
import com.android.server.companion.CompanionExemptionProcessor;
import com.android.server.companion.association.AssociationStore;

import java.io.PrintWriter;
@@ -101,6 +102,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
    private final PowerManagerInternal mPowerManagerInternal;
    @NonNull
    private final UserManager mUserManager;
    @NonNull
    private final CompanionExemptionProcessor mCompanionExemptionProcessor;

    // NOTE: Same association may appear in more than one of the following sets at the same time.
    // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -111,7 +114,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
    @NonNull
    private final Set<Integer> mNearbyBleDevices = new HashSet<>();
    @NonNull
    private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
    private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
    @NonNull
    private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
    @NonNull
@@ -146,7 +149,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
            @NonNull UserManager userManager,
            @NonNull AssociationStore associationStore,
            @NonNull ObservableUuidStore observableUuidStore,
            @NonNull PowerManagerInternal powerManagerInternal) {
            @NonNull PowerManagerInternal powerManagerInternal,
            @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
        mContext = context;
        mCompanionAppBinder = companionAppBinder;
        mAssociationStore = associationStore;
@@ -156,6 +160,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
                mObservableUuidStore, this);
        mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
        mPowerManagerInternal = powerManagerInternal;
        mCompanionExemptionProcessor = companionExemptionProcessor;
    }

    /** Initialize {@link DevicePresenceProcessor} */
@@ -404,7 +409,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
     * nearby (for "self-managed" associations).
     */
    public boolean isDevicePresent(int associationId) {
        return mReportedSelfManagedDevices.contains(associationId)
        return mConnectedSelfManagedDevices.contains(associationId)
                || mConnectedBtDevices.contains(associationId)
                || mNearbyBleDevices.contains(associationId)
                || mSimulated.contains(associationId);
@@ -451,7 +456,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
     * notifyDeviceAppeared()}
     */
    public void onSelfManagedDeviceConnected(int associationId) {
        onDevicePresenceEvent(mReportedSelfManagedDevices,
        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                associationId, EVENT_SELF_MANAGED_APPEARED);
    }

@@ -467,7 +472,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
     * notifyDeviceDisappeared()}
     */
    public void onSelfManagedDeviceDisconnected(int associationId) {
        onDevicePresenceEvent(mReportedSelfManagedDevices,
        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                associationId, EVENT_SELF_MANAGED_DISAPPEARED);
    }

@@ -475,7 +480,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
     * Marks a "self-managed" device as disconnected when binderDied.
     */
    public void onSelfManagedDeviceReporterBinderDied(int associationId) {
        onDevicePresenceEvent(mReportedSelfManagedDevices,
        onDevicePresenceEvent(mConnectedSelfManagedDevices,
                associationId, EVENT_SELF_MANAGED_DISAPPEARED);
    }

@@ -683,6 +688,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene

                if (association.shouldBindWhenPresent()) {
                    bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
                } else {
                    return;
                }
@@ -715,6 +721,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
                // Check if there are other devices associated to the app that are present.
                if (!shouldBindPackage(userId, packageName)) {
                    mCompanionAppBinder.unbindCompanionApp(userId, packageName);
                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
                }
                break;
            default:
@@ -940,7 +947,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene

        mConnectedBtDevices.remove(id);
        mNearbyBleDevices.remove(id);
        mReportedSelfManagedDevices.remove(id);
        mConnectedSelfManagedDevices.remove(id);
        mSimulated.remove(id);
        synchronized (mBtDisconnectedDevices) {
            mBtDisconnectedDevices.remove(id);
@@ -1100,7 +1107,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
        out.append("Companion Device Present: ");
        if (mConnectedBtDevices.isEmpty()
                && mNearbyBleDevices.isEmpty()
                && mReportedSelfManagedDevices.isEmpty()) {
                && mConnectedSelfManagedDevices.isEmpty()) {
            out.append("<empty>\n");
            return;
        } else {
@@ -1130,11 +1137,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
        }

        out.append("  Self-Reported Devices: ");
        if (mReportedSelfManagedDevices.isEmpty()) {
        if (mConnectedSelfManagedDevices.isEmpty()) {
            out.append("<empty>\n");
        } else {
            out.append("\n");
            for (int associationId : mReportedSelfManagedDevices) {
            for (int associationId : mConnectedSelfManagedDevices) {
                AssociationInfo a = mAssociationStore.getAssociationById(associationId);
                out.append("    ").append(a.toShortString()).append('\n');
            }