Fix DomainVerificationService deadlock

Enforces the PackageManagerService lock to be taken before the DVS lock,
when neccessary, so that the locking is always one-way and cannot
deadlock when Settings attempts to serialize state.

Bug: 183643808

Test: manual run CTS which previously reproduced this issue

Change-Id: Ibccde458df16238755f3a67080321cb3ebdf7233
This commit is contained in:
Winson
2021-03-24 15:24:52 -07:00
parent 5f5d4f6071
commit 897a2256c8
9 changed files with 632 additions and 351 deletions

View File

@@ -356,6 +356,7 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.permission.persistence.RuntimePermissionsPersistence;
@@ -1686,9 +1687,8 @@ public class PackageManagerService extends IPackageManager.Stub
private final DomainVerificationConnection mDomainVerificationConnection =
new DomainVerificationConnection();
private class DomainVerificationConnection implements
DomainVerificationService.Connection, DomainVerificationProxyV1.Connection,
DomainVerificationProxyV2.Connection {
private class DomainVerificationConnection implements DomainVerificationService.Connection,
DomainVerificationProxyV1.Connection, DomainVerificationProxyV2.Connection {
@Override
public void scheduleWriteSettings() {
@@ -1732,22 +1732,77 @@ public class PackageManagerService extends IPackageManager.Stub
return callingUid == getPackageUid(packageName, 0, callingUserId);
}
@Nullable
@Override
public PackageSetting getPackageSettingLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackageSetting(pkgName);
}
@Nullable
@Override
public AndroidPackage getPackageLocked(@NonNull String pkgName) {
return PackageManagerService.this.getPackage(pkgName);
}
@Nullable
@Override
public AndroidPackage getPackage(@NonNull String packageName) {
return getPackageLocked(packageName);
return PackageManagerService.this.getPackage(packageName);
}
@NonNull
@Override
public void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block) {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
block.accept(snapshot::getPackageSetting);
}
} else {
block.accept(snapshot::getPackageSetting);
}
}
@Override
public <Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block) {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
}
@Override
public <ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
block.accept(snapshot::getPackageSetting);
}
} else {
block.accept(snapshot::getPackageSetting);
}
}
@Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,
PackageSetting>, Output, ExceptionType> block) throws ExceptionType {
final Computer snapshot = snapshotComputer();
// This method needs to either lock or not lock consistently throughout the method,
// so if the live computer is returned, force a wrapping sync block.
if (snapshot == mLiveComputer) {
synchronized (mLock) {
return block.apply(snapshot::getPackageSetting);
}
} else {
return block.apply(snapshot::getPackageSetting);
}
}
@Override
@@ -2012,7 +2067,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Cached attributes. The names in this class are the same as the
// names in PackageManagerService; see that class for documentation.
private final Settings mSettings;
protected final Settings mSettings;
private final WatchedSparseIntArray mIsolatedOwners;
private final WatchedArrayMap<String, AndroidPackage> mPackages;
private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
@@ -4669,6 +4724,17 @@ public class PackageManagerService extends IPackageManager.Stub
mLock = mService.mLock;
}
/**
* Explicilty snapshot {@link Settings#mPackages} for cases where the caller must not lock
* in order to get package data. It is expected that the caller locks itself to be able
* to block on changes to the package data and bring itself up to date once the change
* propagates to it. Use with heavy caution.
* @return
*/
private Map<String, PackageSetting> snapshotPackageSettings() {
return mSettings.snapshot().mPackages;
}
public @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
String resolvedType, int flags, int userId, int callingUid,
String instantAppPkgName) {
@@ -4800,7 +4866,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Compute read-only functions, based on live data. This attribute may be modified multiple
// times during the PackageManagerService constructor but it should not be modified thereafter.
private Computer mLiveComputer;
private ComputerLocked mLiveComputer;
// A lock-free cache for frequently called functions.
private volatile Computer mSnapshotComputer;
// If true, the snapshot is invalid (stale). The attribute is static since it may be

View File

@@ -28,6 +28,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Binder;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
@@ -35,8 +36,10 @@ import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import com.android.internal.util.FunctionalUtils;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.Settings;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -46,6 +49,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
public interface DomainVerificationManagerInternal {
@@ -108,9 +112,10 @@ public interface DomainVerificationManagerInternal {
int APPROVAL_LEVEL_INSTANT_APP = 5;
/**
* Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
* which sorts packages by approval priority. A higher numerical value means the package should
* override all lower values. This means that comparison using less/greater than IS valid.
* Defines the possible values for
* {@link #approvalLevelForDomain(PackageSetting, Intent, List, int, int)} which sorts packages
* by approval priority. A higher numerical value means the package should override all lower
* values. This means that comparison using less/greater than IS valid.
*
* Negative values are possible, although not implemented, reserved if explicit disable of a
* package for a domain needs to be tracked.
@@ -184,7 +189,7 @@ public interface DomainVerificationManagerInternal {
/**
* Migrates verification state from a previous install to a new one. It is expected that the
* {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
* {@link #generateNewId()}. This will preserve {@link DomainVerificationManager#STATE_SUCCESS}
* {@link #generateNewId()}. This will preserve {@link DomainVerificationState#STATE_SUCCESS}
* domains under the assumption that the new package will pass the same server side config as
* the previous package, as they have matching signatures.
* <p>
@@ -276,7 +281,7 @@ public interface DomainVerificationManagerInternal {
/**
* Until the legacy APIs are entirely removed, returns the legacy state from the previously
* written info stored in {@link com.android.server.pm.Settings}.
* written info stored in {@link Settings}.
*/
int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
@@ -288,15 +293,12 @@ public interface DomainVerificationManagerInternal {
* @param userId the specific user to print, or null to skip printing user selection
* states, supports {@link android.os.UserHandle#USER_ALL}
* @param pkgSettingFunction the method by which to retrieve package data; if this is called
* from {@link com.android.server.pm.PackageManagerService}, it is
* expected to pass in the snapshot of {@link PackageSetting} objects,
* or if null is passed, the manager may decide to lock {@link
* com.android.server.pm.PackageManagerService} through {@link
* Connection#getPackageSettingLocked(String)}
* from {@link PackageManagerService}, it is
* expected to pass in the snapshot of {@link PackageSetting} objects
*/
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId,
@Nullable Function<String, PackageSetting> pkgSettingFunction)
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException;
@NonNull
@@ -368,17 +370,46 @@ public interface DomainVerificationManagerInternal {
*/
void schedule(int code, @Nullable Object object);
// TODO(b/178733426): Make DomainVerificationService PMS snapshot aware so it can avoid
// locking package state at all. This can be as simple as removing this method in favor of
// accepting a PackageSetting function in at every method call, although should probably
// be abstracted to a wrapper class.
@Nullable
PackageSetting getPackageSettingLocked(@NonNull String pkgName);
/**
* Run a function block that requires access to {@link PackageSetting} data. This will
* ensure the {@link PackageManagerService} is taken before
* {@link DomainVerificationManagerInternal}'s lock is taken to avoid deadlock.
*/
void withPackageSettings(@NonNull Consumer<Function<String, PackageSetting>> block);
@Nullable
AndroidPackage getPackageLocked(@NonNull String pkgName);
/**
* Variant which returns a value to the caller.
* @see #withPackageSettings(Consumer)
*/
<Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block);
/**
* Variant which throws.
* @see #withPackageSettings(Consumer)
*/
<ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType;
/**
* Variant which returns a value to the caller and throws.
* @see #withPackageSettings(Consumer)
*/
<Output, ExceptionType extends Exception> Output withPackageSettingsReturningThrowing(
@NonNull ThrowingFunction<Function<String, PackageSetting>, Output, ExceptionType>
block) throws ExceptionType;
@UserIdInt
int[] getAllUserIds();
interface ThrowingConsumer<Input, ExceptionType extends Exception> {
void accept(Input input) throws ExceptionType;
}
interface ThrowingFunction<Input, Output, ExceptionType extends Exception> {
Output apply(Input input) throws ExceptionType;
}
}
}

View File

@@ -38,6 +38,7 @@ import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.Build;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -51,10 +52,12 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FunctionalUtils;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.Settings;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
@@ -73,6 +76,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
public class DomainVerificationService extends SystemService
@@ -100,9 +104,9 @@ public class DomainVerificationService extends SystemService
* immediately attached once its available.
* <p>
* Generally this should be not accessed directly. Prefer calling {@link
* #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}.
* #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function)}.
*
* @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)
* @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function)
**/
@GuardedBy("mLock")
@NonNull
@@ -169,7 +173,12 @@ public class DomainVerificationService extends SystemService
@Override
public void setConnection(@NonNull Connection connection) {
mConnection = connection;
if (Build.IS_USERDEBUG || Build.IS_ENG) {
mConnection = new LockSafeConnection(connection);
} else {
mConnection = connection;
}
mEnforcer.setCallback(mConnection);
}
@@ -250,41 +259,44 @@ public class DomainVerificationService extends SystemService
public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
throws NameNotFoundException {
mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
synchronized (mLock) {
PackageSetting pkgSetting = pkgSettings.apply(packageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
// TODO(b/159952358): Should the domain list be cached?
ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
if (domains.isEmpty()) {
return null;
}
int size = domains.size();
for (int index = 0; index < size; index++) {
hostToStateMap.putIfAbsent(domains.valueAt(index),
DomainVerificationState.STATE_NO_RESPONSE);
}
final int mapSize = hostToStateMap.size();
for (int index = 0; index < mapSize; index++) {
int internalValue = hostToStateMap.valueAt(index);
int publicValue = DomainVerificationState.convertToInfoState(internalValue);
hostToStateMap.setValueAt(index, publicValue);
}
// TODO(b/159952358): Do not return if no values are editable (all ignored states)?
return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
// TODO(b/159952358): Should the domain list be cached?
ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
if (domains.isEmpty()) {
return null;
}
int size = domains.size();
for (int index = 0; index < size; index++) {
hostToStateMap.putIfAbsent(domains.valueAt(index),
DomainVerificationState.STATE_NO_RESPONSE);
}
final int mapSize = hostToStateMap.size();
for (int index = 0; index < mapSize; index++) {
int internalValue = hostToStateMap.valueAt(index);
int publicValue = DomainVerificationState.convertToInfoState(internalValue);
hostToStateMap.setValueAt(index, publicValue);
}
// TODO(b/159952358): Do not return if no values are editable (all ignored states)?
return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
}
});
}
@DomainVerificationManager.Error
@@ -307,44 +319,47 @@ public class DomainVerificationService extends SystemService
@NonNull Set<String> domains, int state)
throws NameNotFoundException {
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
synchronized (mLock) {
List<String> verifiedDomains = new ArrayList<>();
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
synchronized (mLock) {
List<String> verifiedDomains = new ArrayList<>();
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
true /* forAutoVerify */, callingUid, null /* userId */);
if (result.isError()) {
return result.getErrorCode();
}
DomainVerificationPkgState pkgState = result.getPkgState();
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
for (String domain : domains) {
Integer previousState = stateMap.get(domain);
if (previousState != null
&& !DomainVerificationState.isModifiable(previousState)) {
continue;
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
true /* forAutoVerify */, callingUid, null /* userId */,
pkgSettings);
if (result.isError()) {
return result.getErrorCode();
}
if (DomainVerificationState.isVerified(state)) {
verifiedDomains.add(domain);
DomainVerificationPkgState pkgState = result.getPkgState();
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
for (String domain : domains) {
Integer previousState = stateMap.get(domain);
if (previousState != null
&& !DomainVerificationState.isModifiable(previousState)) {
continue;
}
if (DomainVerificationState.isVerified(state)) {
verifiedDomains.add(domain);
}
stateMap.put(domain, state);
}
stateMap.put(domain, state);
int size = verifiedDomains.size();
for (int index = 0; index < size; index++) {
removeUserStatesForDomain(verifiedDomains.get(index));
}
}
int size = verifiedDomains.size();
for (int index = 0; index < size; index++) {
removeUserStatesForDomain(verifiedDomains.get(index));
}
}
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
});
}
@Override
public void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
@Nullable ArraySet<String> domains) throws NameNotFoundException {
@Nullable final ArraySet<String> domains) throws NameNotFoundException {
mEnforcer.assertInternal(mConnection.getCallingUid());
switch (state) {
@@ -359,31 +374,61 @@ public class DomainVerificationService extends SystemService
}
ArraySet<String> verifiedDomains = new ArraySet<>();
if (packageName == null) {
synchronized (mLock) {
ArraySet<String> validDomains = new ArraySet<>();
mConnection.withPackageSettings(pkgSettings -> {
synchronized (mLock) {
ArraySet<String> validDomains = new ArraySet<>();
int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String pkgName = pkgState.getPackageName();
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String pkgName = pkgState.getPackageName();
PackageSetting pkgSetting = pkgSettings.apply(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
}
AndroidPackage pkg = pkgSetting.getPkg();
validDomains.clear();
ArraySet<String> autoVerifyDomains =
mCollector.collectValidAutoVerifyDomains(pkg);
if (domains == null) {
validDomains.addAll(autoVerifyDomains);
} else {
validDomains.addAll(domains);
validDomains.retainAll(autoVerifyDomains);
}
if (DomainVerificationState.isVerified(state)) {
verifiedDomains.addAll(validDomains);
}
setDomainVerificationStatusInternal(pkgState, state, validDomains);
}
}
});
} else {
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
PackageSetting pkgSetting = pkgSettings.apply(packageName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
AndroidPackage pkg = pkgSetting.getPkg();
validDomains.clear();
ArraySet<String> autoVerifyDomains =
mCollector.collectValidAutoVerifyDomains(pkg);
final ArraySet<String> validDomains;
if (domains == null) {
validDomains.addAll(autoVerifyDomains);
validDomains = mCollector.collectValidAutoVerifyDomains(pkg);
} else {
validDomains.addAll(domains);
validDomains.retainAll(autoVerifyDomains);
validDomains = domains;
validDomains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg));
}
if (DomainVerificationState.isVerified(state)) {
@@ -392,32 +437,7 @@ public class DomainVerificationService extends SystemService
setDomainVerificationStatusInternal(pkgState, state, validDomains);
}
}
} else {
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
AndroidPackage pkg = pkgSetting.getPkg();
if (domains == null) {
domains = mCollector.collectValidAutoVerifyDomains(pkg);
} else {
domains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg));
}
if (DomainVerificationState.isVerified(state)) {
verifiedDomains.addAll(domains);
}
setDomainVerificationStatusInternal(pkgState, state, domains);
}
});
}
// Mirror SystemApi behavior of revoking user selection for approved domains.
@@ -518,44 +538,48 @@ public class DomainVerificationService extends SystemService
public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
throws NameNotFoundException {
synchronized (mLock) {
final int callingUid = mConnection.getCallingUid();
// Pass null for package name here and do the app visibility enforcement inside
// getAndValidateAttachedLocked instead, since this has to fail with the same invalid
// ID reason if the target app is invisible
if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
null /* packageName */, userId)) {
return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID;
}
final int callingUid = mConnection.getCallingUid();
// Pass null for package name here and do the app visibility enforcement inside
// getAndValidateAttachedLocked instead, since this has to fail with the same invalid
// ID reason if the target app is invisible
if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
null /* packageName */, userId)) {
return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID;
}
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId);
if (result.isError()) {
return result.getErrorCode();
}
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
synchronized (mLock) {
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId, pkgSettings);
if (result.isError()) {
return result.getErrorCode();
}
DomainVerificationPkgState pkgState = result.getPkgState();
DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(userId);
DomainVerificationPkgState pkgState = result.getPkgState();
DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(
userId);
// Disable other packages if approving this one. Note that this check is only done for
// enabling. This allows an escape hatch in case multiple packages somehow get selected.
// They can be disabled without blocking in a circular dependency.
if (enabled) {
int statusCode = revokeOtherUserSelections(userState, userId, domains);
if (statusCode != DomainVerificationManager.STATUS_OK) {
return statusCode;
// Disable other packages if approving this one. Note that this check is only done
// for enabling. This allows an escape hatch in case multiple packages somehow get
// selected. They can be disabled without blocking in a circular dependency.
if (enabled) {
int statusCode = revokeOtherUserSelectionsLocked(userState, userId, domains,
pkgSettings);
if (statusCode != DomainVerificationManager.STATUS_OK) {
return statusCode;
}
}
if (enabled) {
userState.addHosts(domains);
} else {
userState.removeHosts(domains);
}
}
if (enabled) {
userState.addHosts(domains);
} else {
userState.removeHosts(domains);
}
}
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
});
}
@Override
@@ -563,52 +587,57 @@ public class DomainVerificationService extends SystemService
@NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains)
throws NameNotFoundException {
mEnforcer.assertInternal(mConnection.getCallingUid());
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
PackageSetting pkgSetting = pkgSettings.apply(packageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
Set<String> validDomains =
domains == null ? mCollector.collectAllWebDomains(pkg) : domains;
Set<String> validDomains =
domains == null ? mCollector.collectAllWebDomains(pkg) : domains;
validDomains.retainAll(mCollector.collectAllWebDomains(pkg));
validDomains.retainAll(mCollector.collectAllWebDomains(pkg));
if (userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
if (userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
DomainVerificationInternalUserState userState =
pkgState.getOrCreateUserState(aUserId);
revokeOtherUserSelectionsLocked(userState, aUserId, validDomains,
pkgSettings);
if (enabled) {
userState.addHosts(validDomains);
} else {
userState.removeHosts(validDomains);
}
}
} else {
DomainVerificationInternalUserState userState =
pkgState.getOrCreateUserState(aUserId);
revokeOtherUserSelections(userState, aUserId, validDomains);
pkgState.getOrCreateUserState(userId);
revokeOtherUserSelectionsLocked(userState, userId, validDomains, pkgSettings);
if (enabled) {
userState.addHosts(validDomains);
} else {
userState.removeHosts(validDomains);
}
}
} else {
DomainVerificationInternalUserState userState =
pkgState.getOrCreateUserState(userId);
revokeOtherUserSelections(userState, userId, validDomains);
if (enabled) {
userState.addHosts(validDomains);
} else {
userState.removeHosts(validDomains);
}
}
}
});
mConnection.scheduleWriteSettings();
}
private int revokeOtherUserSelections(@NonNull DomainVerificationInternalUserState userState,
@UserIdInt int userId, @NonNull Set<String> domains) {
@GuardedBy("mLock")
private int revokeOtherUserSelectionsLocked(
@NonNull DomainVerificationInternalUserState userState, @UserIdInt int userId,
@NonNull Set<String> domains,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
// Cache the approved packages from the 1st pass because the search is expensive
ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>();
@@ -617,8 +646,8 @@ public class DomainVerificationService extends SystemService
continue;
}
Pair<List<String>, Integer> packagesToLevel = getApprovedPackages(domain,
userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked);
Pair<List<String>, Integer> packagesToLevel = getApprovedPackagesLocked(domain,
userId, APPROVAL_LEVEL_NONE + 1, pkgSettingFunction);
int highestApproval = packagesToLevel.second;
if (highestApproval > APPROVAL_LEVEL_SELECTION) {
return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE;
@@ -663,46 +692,52 @@ public class DomainVerificationService extends SystemService
mConnection.getCallingUserId(), packageName, userId)) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
synchronized (mLock) {
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
int webDomainsSize = webDomains.size();
Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts();
for (int index = 0; index < webDomainsSize; index++) {
String host = webDomains.valueAt(index);
Integer state = stateMap.get(host);
int domainState;
if (state != null && DomainVerificationState.isVerified(state)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
} else if (enabledHosts.contains(host)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
} else {
domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
synchronized (mLock) {
PackageSetting pkgSetting = pkgSettings.apply(packageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
domains.put(host, domainState);
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
int webDomainsSize = webDomains.size();
Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
Set<String> enabledHosts =
userState == null ? emptySet() : userState.getEnabledHosts();
for (int index = 0; index < webDomainsSize; index++) {
String host = webDomains.valueAt(index);
Integer state = stateMap.get(host);
int domainState;
if (state != null && DomainVerificationState.isVerified(state)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
} else if (enabledHosts.contains(host)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
} else {
domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
}
domains.put(host, domainState);
}
boolean linkHandlingAllowed =
userState == null || userState.isLinkHandlingAllowed();
return new DomainVerificationUserState(pkgState.getId(), packageName,
UserHandle.of(userId), linkHandlingAllowed, domains);
}
boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
return new DomainVerificationUserState(pkgState.getId(), packageName,
UserHandle.of(userId), linkHandlingAllowed, domains);
}
});
}
public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
@@ -710,67 +745,68 @@ public class DomainVerificationService extends SystemService
userId);
SparseArray<List<String>> levelToPackages = new SparseArray<>();
return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
// First, collect the raw approval level values
synchronized (mLock) {
final int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String packageName = pkgState.getPackageName();
PackageSetting pkgSetting = pkgSettings.apply(packageName);
if (pkgSetting == null) {
continue;
}
// First, collect the raw approval level values
synchronized (mLock) {
final int size = mAttachedPkgStates.size();
int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
if (level <= APPROVAL_LEVEL_NONE) {
continue;
}
List<String> list = levelToPackages.get(level);
if (list == null) {
list = new ArrayList<>();
levelToPackages.put(level, list);
}
list.add(packageName);
}
}
final int size = levelToPackages.size();
if (size == 0) {
return emptyList();
}
// Then sort them ascending by first installed time, with package name as tie breaker
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String packageName = pkgState.getPackageName();
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
if (pkgSetting == null) {
continue;
}
levelToPackages.valueAt(index).sort((first, second) -> {
PackageSetting firstPkgSetting = pkgSettings.apply(first);
PackageSetting secondPkgSetting = pkgSettings.apply(second);
int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
if (level <= APPROVAL_LEVEL_NONE) {
continue;
}
List<String> list = levelToPackages.get(level);
if (list == null) {
list = new ArrayList<>();
levelToPackages.put(level, list);
}
list.add(packageName);
long firstInstallTime =
firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
long secondInstallTime =
secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
if (firstInstallTime != secondInstallTime) {
return (int) (firstInstallTime - secondInstallTime);
}
return first.compareToIgnoreCase(second);
});
}
}
final int size = levelToPackages.size();
if (size == 0) {
return emptyList();
}
// Then sort them ascending by first installed time, with package name as the tie breaker
for (int index = 0; index < size; index++) {
levelToPackages.valueAt(index).sort((first, second) -> {
PackageSetting firstPkgSetting = mConnection.getPackageSettingLocked(first);
PackageSetting secondPkgSetting = mConnection.getPackageSettingLocked(second);
long firstInstallTime =
firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
long secondInstallTime =
secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
if (firstInstallTime != secondInstallTime) {
return (int) (firstInstallTime - secondInstallTime);
List<DomainOwner> owners = new ArrayList<>();
for (int index = 0; index < size; index++) {
int level = levelToPackages.keyAt(index);
boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
List<String> packages = levelToPackages.valueAt(index);
int packagesSize = packages.size();
for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
}
return first.compareToIgnoreCase(second);
});
}
List<DomainOwner> owners = new ArrayList<>();
for (int index = 0; index < size; index++) {
int level = levelToPackages.keyAt(index);
boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
List<String> packages = levelToPackages.valueAt(index);
int packagesSize = packages.size();
for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
}
}
return owners;
return owners;
});
}
@NonNull
@@ -1085,21 +1121,15 @@ public class DomainVerificationService extends SystemService
@Override
public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable Integer userId) throws NameNotFoundException {
// This method is only used by DomainVerificationShell, which doesn't lock PMS, so it's
// safe to pass mConnection directly here and lock PMS. This method is not exposed
// to the general system server/PMS.
printState(writer, packageName, userId, mConnection::getPackageSettingLocked);
mConnection.withPackageSettingsThrowing(
pkgSettings -> printState(writer, packageName, userId, pkgSettings));
}
@Override
public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId,
@Nullable Function<String, PackageSetting> pkgSettingFunction)
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException {
if (pkgSettingFunction == null) {
pkgSettingFunction = mConnection::getPackageSettingLocked;
}
synchronized (mLock) {
mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
}
@@ -1148,7 +1178,9 @@ public class DomainVerificationService extends SystemService
@GuardedBy("mLock")
private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
@Nullable Integer userIdForFilter) throws NameNotFoundException {
@Nullable Integer userIdForFilter,
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException {
if (domainSetId == null) {
throw new IllegalArgumentException("domainSetId cannot be null");
}
@@ -1165,7 +1197,7 @@ public class DomainVerificationService extends SystemService
return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
}
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
PackageSetting pkgSetting = pkgSettingFunction.apply(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
}
@@ -1253,31 +1285,33 @@ public class DomainVerificationService extends SystemService
@Override
public void clearDomainVerificationState(@Nullable List<String> packageNames) {
mEnforcer.assertInternal(mConnection.getCallingUid());
synchronized (mLock) {
if (packageNames == null) {
int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String pkgName = pkgState.getPackageName();
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
mConnection.withPackageSettings(pkgSettings -> {
synchronized (mLock) {
if (packageNames == null) {
int size = mAttachedPkgStates.size();
for (int index = 0; index < size; index++) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
String pkgName = pkgState.getPackageName();
PackageSetting pkgSetting = pkgSettings.apply(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
}
resetDomainState(pkgState, pkgSetting.getPkg());
}
resetDomainState(pkgState, pkgSetting.getPkg());
}
} else {
int size = packageNames.size();
for (int index = 0; index < size; index++) {
String pkgName = packageNames.get(index);
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
} else {
int size = packageNames.size();
for (int index = 0; index < size; index++) {
String pkgName = packageNames.get(index);
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
PackageSetting pkgSetting = pkgSettings.apply(pkgName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
continue;
}
resetDomainState(pkgState, pkgSetting.getPkg());
}
resetDomainState(pkgState, pkgSetting.getPkg());
}
}
}
});
mConnection.scheduleWriteSettings();
}
@@ -1692,8 +1726,9 @@ public class DomainVerificationService extends SystemService
/**
* @return the filtered list paired with the corresponding approval level
*/
@GuardedBy("mLock")
@NonNull
private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain,
@UserIdInt int userId, int minimumApproval,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
int highestApproval = minimumApproval;
@@ -1792,4 +1827,93 @@ public class DomainVerificationService extends SystemService
return mErrorCode;
}
}
/**
* Wraps a {@link Connection} to verify that the {@link PackageSetting} calls do not hold
* {@link #mLock}, as that can cause deadlock when {@link Settings} tries to serialize state to
* disk. Only enabled if {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} is true.
*/
private class LockSafeConnection implements Connection {
@NonNull
private final Connection mConnection;
private LockSafeConnection(@NonNull Connection connection) {
mConnection = connection;
}
private void enforceLocking() {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Method should not hold DVS lock when accessing package data");
}
}
@Override
public void withPackageSettings(
@NonNull Consumer<Function<String, PackageSetting>> block) {
enforceLocking();
mConnection.withPackageSettings(block);
}
@Override
public <Output> Output withPackageSettingsReturning(
@NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageSetting>, Output>
block) {
enforceLocking();
return mConnection.withPackageSettingsReturning(block);
}
@Override
public <ExceptionType extends Exception> void withPackageSettingsThrowing(
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType {
enforceLocking();
mConnection.withPackageSettingsThrowing(block);
}
@Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,
PackageSetting>, Output, ExceptionType> block) throws ExceptionType {
enforceLocking();
return mConnection.withPackageSettingsReturningThrowing(block);
}
@Override
public void scheduleWriteSettings() {
mConnection.scheduleWriteSettings();
}
@Override
public int getCallingUid() {
return mConnection.getCallingUid();
}
@Override
@UserIdInt
public int getCallingUserId() {
return mConnection.getCallingUserId();
}
@Override
public void schedule(int code, @Nullable Object object) {
mConnection.schedule(code, object);
}
@Override
@UserIdInt
public int[] getAllUserIds() {
return mConnection.getAllUserIds();
}
@Override
public boolean filterAppAccess(@NonNull String packageName, int callingUid, int userId) {
return mConnection.filterAppAccess(packageName, callingUid, userId);
}
@Override
public boolean doesUserExist(int userId) {
return mConnection.doesUserExist(userId);
}
}
}

View File

@@ -31,6 +31,7 @@ import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationEnforcer
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
@@ -98,10 +99,13 @@ class DomainVerificationEnforcerTest {
mockThrowOnUnmocked {
whenever(callingUid) { callingUidInt.get() }
whenever(callingUserId) { callingUserIdInt.get() }
whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
mockPackageSettings {
when (it) {
VISIBLE_PKG -> visiblePkgSetting
INVISIBLE_PKG -> invisiblePkgSetting
else -> null
}
}
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())
whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }

View File

@@ -32,6 +32,7 @@ import android.os.Process
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -445,11 +446,8 @@ class DomainVerificationManagerApiTest {
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)?.getPkg()
mockPackageSettings {
pkgSettingFunction(it)
}
})
}

View File

@@ -36,6 +36,7 @@ import android.util.ArraySet
import android.util.Xml
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -380,11 +381,8 @@ class DomainVerificationPackageTest {
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!.getPkg()
mockPackageSettings {
pkgSettingFunction(it)
}
})
}

View File

@@ -29,6 +29,7 @@ import android.util.ArraySet
import android.util.SparseArray
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
@@ -256,8 +257,12 @@ class DomainVerificationSettingsMutationTest {
mockThrowOnUnmocked {
whenever(callingUid) { TEST_UID }
whenever(callingUserId) { TEST_USER_ID }
whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting() }
whenever(getPackageLocked(TEST_PKG)) { mockPkg() }
mockPackageSettings {
when (it) {
TEST_PKG -> mockPkgSetting()
else -> null
}
}
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain
import com.android.internal.util.FunctionalUtils
import com.android.server.pm.PackageSetting
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.testutils.whenever
import org.mockito.ArgumentMatchers.any
import java.util.function.Consumer
import java.util.function.Function
internal object DomainVerificationTestUtils {
@Suppress("UNCHECKED_CAST")
fun DomainVerificationManagerInternal.Connection.mockPackageSettings(
block: (String) -> PackageSetting?
) {
whenever(withPackageSettings(any())) {
(arguments[0] as Consumer<Function<String, PackageSetting?>>).accept { block(it) }
}
whenever(withPackageSettingsReturning<Any>(any())) {
(arguments[0] as FunctionalUtils.ThrowingFunction<Function<String, PackageSetting?>, *>)
.apply { block(it) }
}
whenever(withPackageSettingsThrowing<Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingConsumer<
Function<String, PackageSetting?>, *>)
.accept { block(it) }
}
whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction<
Function<String, PackageSetting?>, *, *>)
.apply { block(it) }
}
}
}

View File

@@ -30,6 +30,7 @@ import android.os.Process
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageSettings
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -83,10 +84,13 @@ class DomainVerificationUserStateOverrideTest {
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 }
whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 }
whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() }
whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() }
mockPackageSettings {
when (it) {
PKG_ONE -> pkg1
PKG_TWO -> pkg2
else -> null
}
}
})
addPackage(pkg1)
addPackage(pkg2)