Merge "Implement domain verification backup and restore" into sc-dev

This commit is contained in:
Winson Chiu
2021-04-30 16:17:30 +00:00
committed by Android (Google) Code Review
12 changed files with 421 additions and 119 deletions

View File

@@ -310,8 +310,8 @@ interface IPackageManager {
void restorePreferredActivities(in byte[] backup, int userId);
byte[] getDefaultAppsBackup(int userId);
void restoreDefaultApps(in byte[] backup, int userId);
byte[] getIntentFilterVerificationBackup(int userId);
void restoreIntentFilterVerification(in byte[] backup, int userId);
byte[] getDomainVerificationBackup(int userId);
void restoreDomainVerification(in byte[] backup, int userId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them

View File

@@ -16,10 +16,12 @@
package com.android.server.backup;
import android.annotation.StringDef;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.backup.BlobBackupHelper;
import android.content.pm.IPackageManager;
import android.os.UserHandle;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.util.Slog;
public class PreferredActivityBackupHelper extends BlobBackupHelper {
@@ -27,7 +29,7 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
private static final boolean DEBUG = false;
// current schema of the backup state blob
private static final int STATE_VERSION = 3;
private static final int STATE_VERSION = 4;
// key under which the preferred-activity state blob is committed to backup
private static final String KEY_PREFERRED = "preferred-activity";
@@ -35,14 +37,41 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
// key for default-browser [etc] state
private static final String KEY_DEFAULT_APPS = "default-apps";
// intent-filter verification state
/**
* Intent-filter verification state
* @deprecated Replaced by {@link #KEY_DOMAIN_VERIFICATION}, retained to ensure the key is
* never reused.
*/
@Deprecated
private static final String KEY_INTENT_VERIFICATION = "intent-verification";
public PreferredActivityBackupHelper() {
super(STATE_VERSION,
KEY_PREFERRED,
KEY_DEFAULT_APPS,
KEY_INTENT_VERIFICATION);
/**
* State for {@link DomainVerificationManager}.
*/
private static final String KEY_DOMAIN_VERIFICATION = "domain-verification";
private static final String[] KEYS = new String[] {
KEY_PREFERRED,
KEY_DEFAULT_APPS,
KEY_INTENT_VERIFICATION,
KEY_DOMAIN_VERIFICATION
};
@StringDef(value = {
KEY_PREFERRED,
KEY_DEFAULT_APPS,
KEY_INTENT_VERIFICATION,
KEY_DOMAIN_VERIFICATION
})
private @interface Key {
}
@UserIdInt
private final int mUserId;
public PreferredActivityBackupHelper(@UserIdInt int userId) {
super(STATE_VERSION, KEYS);
mUserId = userId;
}
@Override
@@ -52,14 +81,16 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
Slog.d(TAG, "Handling backup of " + key);
}
try {
// TODO: http://b/22388012
switch (key) {
case KEY_PREFERRED:
return pm.getPreferredActivityBackup(UserHandle.USER_SYSTEM);
return pm.getPreferredActivityBackup(mUserId);
case KEY_DEFAULT_APPS:
return pm.getDefaultAppsBackup(UserHandle.USER_SYSTEM);
return pm.getDefaultAppsBackup(mUserId);
case KEY_INTENT_VERIFICATION:
return pm.getIntentFilterVerificationBackup(UserHandle.USER_SYSTEM);
// Deprecated
return null;
case KEY_DOMAIN_VERIFICATION:
return pm.getDomainVerificationBackup(mUserId);
default:
Slog.w(TAG, "Unexpected backup key " + key);
}
@@ -70,22 +101,24 @@ public class PreferredActivityBackupHelper extends BlobBackupHelper {
}
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
protected void applyRestoredPayload(@Key String key, byte[] payload) {
IPackageManager pm = AppGlobals.getPackageManager();
if (DEBUG) {
Slog.d(TAG, "Handling restore of " + key);
}
try {
// TODO: http://b/22388012
switch (key) {
case KEY_PREFERRED:
pm.restorePreferredActivities(payload, UserHandle.USER_SYSTEM);
pm.restorePreferredActivities(payload, mUserId);
break;
case KEY_DEFAULT_APPS:
pm.restoreDefaultApps(payload, UserHandle.USER_SYSTEM);
pm.restoreDefaultApps(payload, mUserId);
break;
case KEY_INTENT_VERIFICATION:
pm.restoreIntentFilterVerification(payload, UserHandle.USER_SYSTEM);
// Deprecated
break;
case KEY_DOMAIN_VERIFICATION:
pm.restoreDomainVerification(payload, mUserId);
break;
default:
Slog.w(TAG, "Unexpected restore key " + key);

View File

@@ -94,7 +94,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
mUserId = user.getIdentifier();
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId));
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));

View File

@@ -1781,6 +1781,24 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
@Override
public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
withPackageSettingsThrowing2(
@NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
ExceptionTwo> block) throws ExceptionOne, ExceptionTwo {
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,
@@ -22459,21 +22477,44 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
public byte[] getIntentFilterVerificationBackup(int userId) {
public byte[] getDomainVerificationBackup(int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()");
throw new SecurityException("Only the system may call getDomainVerificationBackup()");
}
// TODO(b/170746586)
return null;
try {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
TypedXmlSerializer serializer = Xml.resolveSerializer(output);
mDomainVerificationManager.writeSettings(serializer, true, userId);
return output.toByteArray();
}
} catch (Exception e) {
if (DEBUG_BACKUP) {
Slog.e(TAG, "Unable to write domain verification for backup", e);
}
return null;
}
}
@Override
public void restoreIntentFilterVerification(byte[] backup, int userId) {
public void restoreDomainVerification(byte[] backup, int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system may call restorePreferredActivities()");
}
// TODO(b/170746586)
try {
ByteArrayInputStream input = new ByteArrayInputStream(backup);
TypedXmlPullParser parser = Xml.resolvePullParser(input);
// User ID input isn't necessary here as it assumes the user integers match and that
// the only states inside the backup XML are for the target user.
mDomainVerificationManager.restoreSettings(parser);
input.close();
} catch (Exception e) {
if (DEBUG_BACKUP) {
Slog.e(TAG, "Exception restoring domain verification: " + e.getMessage());
}
}
}
@Override

View File

@@ -2339,7 +2339,8 @@ public final class Settings implements Watchable, Snappable {
}
}
mDomainVerificationManager.writeSettings(serializer);
mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */,
UserHandle.USER_ALL);
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
@@ -2913,13 +2914,7 @@ public final class Settings implements Watchable, Snappable {
}
str.close();
} catch (XmlPullParserException e) {
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
} catch (java.io.IOException e) {
} catch (IOException | XmlPullParserException e) {
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);

View File

@@ -234,8 +234,8 @@ public interface DomainVerificationManagerInternal {
* This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
* lock. This should never be called from within the domain verification classes themselves.
* <p>
* This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
* caller.
* This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
* handled by the caller.
*/
void addPackage(@NonNull PackageSetting newPkgSetting);
@@ -249,20 +249,27 @@ public interface DomainVerificationManagerInternal {
* This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
* lock. This should never be called from within the domain verification classes themselves.
* <p>
* This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
* caller.
* This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
* handled by the caller.
*/
void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting);
/**
* Serializes the entire internal state. This is equivalent to a full backup of the existing
* verification state. This write includes legacy state, as a sibling tag the modern state.
*
* @param includeSignatures Whether to include the package signatures in the output, mainly
* used for backing up the user settings and ensuring they're
* re-attached to the same package.
* @param userId The user to write out. Supports {@link UserHandle#USER_ALL} if all users
* should be written.
*/
void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException;
void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
@UserIdInt int userId) throws IOException;
/**
* Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
* #writeSettings(TypedXmlSerializer)}. Assumes that the
* #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
* {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered.
* <p>
* This is expected to only be used to re-attach states for packages already known to be on the
@@ -298,7 +305,7 @@ public interface DomainVerificationManagerInternal {
/**
* Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
* #writeSettings(TypedXmlSerializer)}. Assumes that the
* #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
* {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
* tag has already been entered.
* <p>
@@ -403,7 +410,7 @@ public interface DomainVerificationManagerInternal {
/**
* Notify that a settings change has been made and that eventually
* {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent.
* {@link #writeSettings(TypedXmlSerializer, boolean, int)} should be invoked by the parent.
*/
void scheduleWriteSettings();
@@ -446,6 +453,15 @@ public interface DomainVerificationManagerInternal {
@NonNull ThrowingConsumer<Function<String, PackageSetting>, ExceptionType> block)
throws ExceptionType;
/**
* Variant which throws 2 exceptions.
* @see #withPackageSettings(Consumer)
*/
<ExceptionOne extends Exception, ExceptionTwo extends Exception> void
withPackageSettingsThrowing2(
@NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
ExceptionTwo> block) throws ExceptionOne, ExceptionTwo;
/**
* Variant which returns a value to the caller and throws.
* @see #withPackageSettings(Consumer)
@@ -461,6 +477,11 @@ public interface DomainVerificationManagerInternal {
void accept(Input input) throws ExceptionType;
}
interface Throwing2Consumer<Input, ExceptionOne extends Exception,
ExceptionTwo extends Exception> {
void accept(Input input) throws ExceptionOne, ExceptionTwo;
}
interface ThrowingFunction<Input, Output, ExceptionType extends Exception> {
Output apply(Input input) throws ExceptionType;
}

View File

@@ -18,8 +18,10 @@ package com.android.server.pm.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -77,7 +79,8 @@ public class DomainVerificationPersistence {
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
@NonNull ArrayMap<String, DomainVerificationPkgState> pending,
@NonNull ArrayMap<String, DomainVerificationPkgState> restored,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
@UserIdInt int userId, @Nullable Function<String, String> pkgNameToSignature)
throws IOException {
try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
try (SettingsXml.WriteSection ignored = serializer.startSection(
TAG_DOMAIN_VERIFICATIONS)) {
@@ -98,26 +101,27 @@ public class DomainVerificationPersistence {
}
try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
writePackageStates(activeSection, active, pkgNameToSignature);
writePackageStates(activeSection, active, userId, pkgNameToSignature);
}
try (SettingsXml.WriteSection restoredSection = serializer.startSection(
TAG_RESTORED)) {
writePackageStates(restoredSection, restored.values(), pkgNameToSignature);
writePackageStates(restoredSection, restored.values(), userId,
pkgNameToSignature);
}
}
}
}
private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
@NonNull Collection<DomainVerificationPkgState> states,
@NonNull Collection<DomainVerificationPkgState> states, int userId,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
if (states.isEmpty()) {
return;
}
for (DomainVerificationPkgState state : states) {
writePkgStateToXml(section, state, pkgNameToSignature);
writePkgStateToXml(section, state, userId, pkgNameToSignature);
}
}
@@ -211,7 +215,7 @@ public class DomainVerificationPersistence {
}
private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
@NonNull DomainVerificationPkgState pkgState,
@NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
@Nullable Function<String, String> pkgNameToSignature) throws IOException {
String packageName = pkgState.getPackageName();
String signature = pkgNameToSignature == null
@@ -231,11 +235,12 @@ public class DomainVerificationPersistence {
pkgState.isHasAutoVerifyDomains())
.attribute(ATTR_SIGNATURE, signature)) {
writeStateMap(parentSection, pkgState.getStateMap());
writeUserStates(parentSection, pkgState.getUserStates());
writeUserStates(parentSection, userId, pkgState.getUserStates());
}
}
private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
@UserIdInt int userId,
@NonNull SparseArray<DomainVerificationInternalUserState> states) throws IOException {
int size = states.size();
if (size == 0) {
@@ -243,8 +248,15 @@ public class DomainVerificationPersistence {
}
try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
for (int index = 0; index < size; index++) {
writeUserStateToXml(section, states.valueAt(index));
if (userId == UserHandle.USER_ALL) {
for (int index = 0; index < size; index++) {
writeUserStateToXml(section, states.valueAt(index));
}
} else {
DomainVerificationInternalUserState userState = states.get(userId);
if (userState != null) {
writeUserStateToXml(section, userState);
}
}
}
}
@@ -278,7 +290,7 @@ public class DomainVerificationPersistence {
return null;
}
boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, false);
ArraySet<String> enabledHosts = new ArraySet<>();
SettingsXml.ChildSection child = section.children();

View File

@@ -129,10 +129,10 @@ public class DomainVerificationService extends SystemService
private final PlatformCompat mPlatformCompat;
@NonNull
private final DomainVerificationSettings mSettings;
private final DomainVerificationCollector mCollector;
@NonNull
private final DomainVerificationCollector mCollector;
private final DomainVerificationSettings mSettings;
@NonNull
private final DomainVerificationEnforcer mEnforcer;
@@ -159,8 +159,8 @@ public class DomainVerificationService extends SystemService
super(context);
mSystemConfig = systemConfig;
mPlatformCompat = platformCompat;
mSettings = new DomainVerificationSettings();
mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
mSettings = new DomainVerificationSettings(mCollector);
mEnforcer = new DomainVerificationEnforcer(context);
mDebug = new DomainVerificationDebug(mCollector);
mShell = new DomainVerificationShell(this);
@@ -1045,21 +1045,29 @@ public class DomainVerificationService extends SystemService
}
@Override
public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
public void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
@UserIdInt int userId)
throws IOException {
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
mSettings.writeSettings(serializer, mAttachedPkgStates, pkgName -> {
PackageSetting pkgSetting = pkgSettings.apply(pkgName);
if (pkgSetting == null) {
// If querying for a user restored package that isn't installed on the
// device yet, there will be no signature to write out. In that case,
// it's expected that this returns null and it falls back to the restored
// state's stored signature if it exists.
return null;
}
Function<String, String> pkgNameToSignature = null;
if (includeSignatures) {
pkgNameToSignature = pkgName -> {
PackageSetting pkgSetting = pkgSettings.apply(pkgName);
if (pkgSetting == null) {
// If querying for a user restored package that isn't installed on the
// device yet, there will be no signature to write out. In that case,
// it's expected that this returns null and it falls back to the
// restored state's stored signature if it exists.
return null;
}
return PackageUtils.computeSignaturesSha256Digest(pkgSetting.getSignatures());
});
return PackageUtils.computeSignaturesSha256Digest(
pkgSetting.getSignatures());
};
}
mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature);
}
});
@@ -1067,11 +1075,14 @@ public class DomainVerificationService extends SystemService
}
@Override
public void readSettings(@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
synchronized (mLock) {
mSettings.readSettings(parser, mAttachedPkgStates);
}
public void readSettings(@NonNull TypedXmlPullParser parser) throws IOException,
XmlPullParserException {
mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2(
pkgSettings -> {
synchronized (mLock) {
mSettings.readSettings(parser, mAttachedPkgStates, pkgSettings);
}
});
}
@Override
@@ -1083,9 +1094,12 @@ public class DomainVerificationService extends SystemService
@Override
public void restoreSettings(@NonNull TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
synchronized (mLock) {
mSettings.restoreSettings(parser, mAttachedPkgStates);
}
mConnection.<IOException, XmlPullParserException>withPackageSettingsThrowing2(
pkgSettings -> {
synchronized (mLock) {
mSettings.restoreSettings(parser, mAttachedPkgStates, pkgSettings);
}
});
}
@Override
@@ -2030,6 +2044,15 @@ public class DomainVerificationService extends SystemService
mConnection.withPackageSettingsThrowing(block);
}
@Override
public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
withPackageSettingsThrowing2(
@NonNull Throwing2Consumer<Function<String, PackageSetting>, ExceptionOne,
ExceptionTwo> block) throws ExceptionOne, ExceptionTwo {
enforceLocking();
mConnection.withPackageSettingsThrowing2(block);
}
@Override
public <Output, ExceptionType extends Exception> Output
withPackageSettingsReturningThrowing(@NonNull ThrowingFunction<Function<String,

View File

@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -29,6 +28,8 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.PackageSetting;
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;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
@@ -36,10 +37,15 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.function.Function;
class DomainVerificationSettings {
@NonNull
private final DomainVerificationCollector mCollector;
/**
* States read from disk that have yet to attach to a package, but are expected to, generally in
* the context of scanning packages already on disk. This is expected to be empty once the boot
@@ -67,24 +73,35 @@ class DomainVerificationSettings {
*/
private final Object mLock = new Object();
public DomainVerificationSettings(@NonNull DomainVerificationCollector collector) {
mCollector = collector;
}
public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
@NonNull Function<String, String> pkgSignatureFunction) throws IOException {
@NonNull Function<String, String> pkgSignatureFunction) {
}
public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
@UserIdInt int userId, @NonNull Function<String, String> pkgSignatureFunction)
throws IOException {
synchronized (mLock) {
DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
mPendingPkgStates, mRestoredPkgStates, pkgSignatureFunction);
mPendingPkgStates, mRestoredPkgStates, userId, pkgSignatureFunction);
}
}
/**
* Parses a previously stored set of states and merges them with {@param liveState}, directly
* mutating the values. This is intended for reading settings written by {@link
* #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device
* setup.
* #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, int, Function)} on the same
* device setup.
*/
public void readSettings(@NonNull TypedXmlPullParser parser,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws IOException, XmlPullParserException {
DomainVerificationPersistence.ReadResult result =
DomainVerificationPersistence.readFromXml(parser);
@@ -101,7 +118,7 @@ class DomainVerificationSettings {
// This branch should never be possible. Settings should be read from disk
// before any states are attached. But just in case, handle it.
if (!existingState.getId().equals(pkgState.getId())) {
mergePkgState(existingState, pkgState);
mergePkgState(existingState, pkgState, pkgSettingFunction);
}
} else {
mPendingPkgStates.put(pkgName, pkgState);
@@ -121,7 +138,8 @@ class DomainVerificationSettings {
* mutating the values. This is intended for restoration across device setups.
*/
public void restoreSettings(@NonNull TypedXmlPullParser parser,
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
@NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws IOException, XmlPullParserException {
// TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
// a new device.
@@ -148,7 +166,7 @@ class DomainVerificationSettings {
}
if (existingState != null) {
mergePkgState(existingState, newState);
mergePkgState(existingState, newState, pkgSettingFunction);
} else {
// If there's no existing state, that means the new state has to be transformed
// in preparation for attaching to brand new package that may eventually be
@@ -190,31 +208,34 @@ class DomainVerificationSettings {
* specific error codes are fresher than the restored state. Essentially state is only restored
* to grant additional verifications to an app.
* <p>
* For user selection state, presence in either state will be considered an enabled host. NOTE:
* only {@link UserHandle#USER_SYSTEM} is merged. There is no restore path in place for
* multiple users.
* <p>
* TODO(b/170746586): Figure out the restore path for multiple users
* <p>
* This will mutate {@param oldState} to contain the merged state.
* For user selection state, presence in either state will be considered an enabled host. This
* assumes that all user IDs on the device match. If this isn't the case, then restore may set
* unexpected values.
*
* NOTE: This will mutate {@param oldState} to contain the merged state.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public static void mergePkgState(@NonNull DomainVerificationPkgState oldState,
@NonNull DomainVerificationPkgState newState) {
public void mergePkgState(@NonNull DomainVerificationPkgState oldState,
@NonNull DomainVerificationPkgState newState,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
PackageSetting pkgSetting = pkgSettingFunction.apply(oldState.getPackageName());
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
Set<String> validDomains = pkg == null
? Collections.emptySet() : mCollector.collectValidAutoVerifyDomains(pkg);
ArrayMap<String, Integer> oldStateMap = oldState.getStateMap();
ArrayMap<String, Integer> newStateMap = newState.getStateMap();
int size = newStateMap.size();
for (int index = 0; index < size; index++) {
String domain = newStateMap.keyAt(index);
Integer newStateCode = newStateMap.valueAt(index);
Integer oldStateCodeInteger = oldStateMap.get(domain);
if (oldStateCodeInteger == null) {
if (!validDomains.contains(domain)) {
// Cannot add domains to an app
continue;
}
int oldStateCode = oldStateCodeInteger;
if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
Integer oldStateCode = oldStateMap.get(domain);
if (oldStateCode == null || oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
if (newStateCode == DomainVerificationState.STATE_SUCCESS
|| newStateCode == DomainVerificationState.STATE_RESTORED) {
oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED);
@@ -228,21 +249,21 @@ class DomainVerificationSettings {
SparseArray<DomainVerificationInternalUserState> newSelectionStates =
newState.getUserStates();
DomainVerificationInternalUserState newUserState =
newSelectionStates.get(UserHandle.USER_SYSTEM);
if (newUserState != null) {
ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
DomainVerificationInternalUserState oldUserState =
oldSelectionStates.get(UserHandle.USER_SYSTEM);
boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
if (oldUserState == null) {
oldUserState = new DomainVerificationInternalUserState(UserHandle.USER_SYSTEM,
newEnabledHosts, linkHandlingAllowed);
oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
} else {
oldUserState.addHosts(newEnabledHosts)
.setLinkHandlingAllowed(linkHandlingAllowed);
final int userStateSize = newSelectionStates.size();
for (int index = 0; index < userStateSize; index++) {
int userId = newSelectionStates.keyAt(index);
DomainVerificationInternalUserState newUserState = newSelectionStates.valueAt(index);
if (newUserState != null) {
ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
DomainVerificationInternalUserState oldUserState = oldSelectionStates.get(userId);
boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
if (oldUserState == null) {
oldSelectionStates.put(userId, new DomainVerificationInternalUserState(userId,
newEnabledHosts, linkHandlingAllowed));
} else {
oldUserState.addHosts(newEnabledHosts)
.setLinkHandlingAllowed(linkHandlingAllowed);
}
}
}
}

View File

@@ -48,6 +48,8 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.UUID
class DomainVerificationPackageTest {
@@ -513,12 +515,141 @@ class DomainVerificationPackageTest {
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun backupAndRestore() {
// This test acts as a proxy for true user restore through PackageManager,
// as that's much harder to test for real.
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, SIGNATURE_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, SIGNATURE_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
val serviceBefore = makeService(pkg1, pkg2)
serviceBefore.addPackage(pkg1)
serviceBefore.addPackage(pkg2)
serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS)
serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.getName(), false, 1)
serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
serviceBefore.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_3), true, 1)
fun assertExpectedState(service: DomainVerificationService) {
service.assertState(
pkg1, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
service.assertState(
pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
service.assertState(
pkg2, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE
)
)
service.assertState(
pkg2, userId = 1, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_SELECTED,
)
)
}
assertExpectedState(serviceBefore)
val backupUser0 = ByteArrayOutputStream().use {
serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0)
it.toByteArray()
}
val backupUser1 = ByteArrayOutputStream().use {
serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 1)
it.toByteArray()
}
val serviceAfter = makeService(pkg1, pkg2)
serviceAfter.addPackage(pkg1)
serviceAfter.addPackage(pkg2)
// Check the state is default before the restoration applies
listOf(0, 1).forEach {
serviceAfter.assertState(
pkg1, userId = it, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
}
listOf(0, 1).forEach {
serviceAfter.assertState(
pkg2, userId = it, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_NONE,
)
)
}
ByteArrayInputStream(backupUser1).use {
serviceAfter.restoreSettings(Xml.resolvePullParser(it))
}
// Assert user 1 was restored
serviceAfter.assertState(
pkg1, userId = 1, linkHandingAllowed = false, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
serviceAfter.assertState(
pkg2, userId = 1, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_SELECTED,
)
)
// User 0 has domain verified (since that's not user-specific)
serviceAfter.assertState(
pkg1, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_NONE,
)
)
// But user 0 is missing any user selected state
serviceAfter.assertState(
pkg2, userId = 0, hostToStateMap = mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
DOMAIN_3 to DOMAIN_STATE_NONE,
)
)
ByteArrayInputStream(backupUser0).use {
serviceAfter.restoreSettings(Xml.resolvePullParser(it))
}
assertExpectedState(serviceAfter)
}
private fun DomainVerificationService.getInfo(pkgName: String) =
getDomainVerificationInfo(pkgName)
.also { assertThat(it).isNotNull() }!!
private fun DomainVerificationService.getUserState(pkgName: String) =
getDomainVerificationUserState(pkgName, USER_ID)
private fun DomainVerificationService.getUserState(pkgName: String, userId: Int = USER_ID) =
getDomainVerificationUserState(pkgName, userId)
.also { assertThat(it).isNotNull() }!!
private fun makeService(
@@ -600,8 +731,24 @@ class DomainVerificationPackageTest {
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
whenever(readUserState(USER_ID)) { PackageUserState() }
whenever(readUserState(0)) { PackageUserState() }
whenever(readUserState(1)) { PackageUserState() }
whenever(signatures) { arrayOf(Signature(signature)) }
whenever(isSystem) { isSystemApp }
}
private fun DomainVerificationService.assertState(
pkg: PackageSetting,
userId: Int,
linkHandingAllowed: Boolean = true,
hostToStateMap: Map<String, Int>
) {
getUserState(pkg.getName(), userId).apply {
assertThat(this.packageName).isEqualTo(pkg.getName())
assertThat(this.identifier).isEqualTo(pkg.domainSetId)
assertThat(this.isLinkHandlingAllowed).isEqualTo(linkHandingAllowed)
assertThat(this.user.identifier).isEqualTo(userId)
assertThat(this.hostToStateMap).containsExactlyEntriesIn(hostToStateMap)
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationState
import android.os.UserHandle
import android.util.ArrayMap
import android.util.SparseArray
import android.util.TypedXmlPullParser
@@ -110,7 +111,8 @@ class DomainVerificationPersistenceTest {
fun writeAndReadBackNormal() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null)
DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
UserHandle.USER_ALL, null)
}
val xml = file.readText()
@@ -128,7 +130,8 @@ class DomainVerificationPersistenceTest {
fun writeAndReadBackWithSignature() {
val (attached, pending, restored) = mockWriteValues()
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
UserHandle.USER_ALL) {
"SIGNATURE_$it"
}
}
@@ -156,7 +159,8 @@ class DomainVerificationPersistenceTest {
fun writeStateSignatureIfFunctionReturnsNull() {
val (attached, pending, restored) = mockWriteValues { "SIGNATURE_$it" }
val file = tempFolder.newFile().writeXml {
DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null }
DomainVerificationPersistence.writeToXml(it, attached, pending, restored,
UserHandle.USER_ALL) { null }
}
val (readActive, readRestored) = file.readXml {
@@ -254,7 +258,7 @@ class DomainVerificationPersistenceTest {
>
<state/>
<user-states>
<user-state userId="1" allowLinkHandling="false">
<user-state userId="1">
<enabled-hosts>
<host name="example-user1.com"/>
<host name="example-user1.org"/>

View File

@@ -42,6 +42,11 @@ internal object DomainVerificationTestUtils {
Function<String, PackageSetting?>, *>)
.accept { block(it) }
}
whenever(withPackageSettingsThrowing2<Exception, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.Throwing2Consumer<
Function<String, PackageSetting?>, *, *>)
.accept { block(it) }
}
whenever(withPackageSettingsReturningThrowing<Any, Exception>(any())) {
(arguments[0] as DomainVerificationManagerInternal.Connection.ThrowingFunction<
Function<String, PackageSetting?>, *, *>)