summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt75
-rw-r--r--core/java/android/app/SystemServiceRegistry.java17
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/Intent.java21
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageManager.java40
-rw-r--r--core/java/android/content/pm/PackageUserState.java14
-rw-r--r--core/java/android/content/pm/parsing/ParsingPackageUtils.java4
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl19
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationInfo.java327
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationManager.java354
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java194
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationRequest.java190
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationState.java100
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl19
-rw-r--r--core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java342
-rw-r--r--core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl44
-rw-r--r--core/java/android/content/pm/verify/domain/TEST_MAPPING12
-rw-r--r--core/java/android/util/SparseArray.java43
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java15
-rw-r--r--core/java/com/android/internal/util/Parcelling.java15
-rw-r--r--core/java/com/android/server/SystemConfig.java5
-rw-r--r--core/res/AndroidManifest.xml21
-rw-r--r--services/core/java/com/android/server/pm/DumpState.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java1184
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java152
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java53
-rw-r--r--services/core/java/com/android/server/pm/Settings.java382
-rw-r--r--services/core/java/com/android/server/pm/SettingsXml.java404
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java200
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java229
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java128
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java228
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java258
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java121
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java36
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java313
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java1226
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java271
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java502
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java70
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java251
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java122
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java185
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java125
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java54
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java21
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java257
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java106
-rw-r--r--services/java/com/android/server/SystemServer.java10
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt10
-rw-r--r--services/tests/PackageManagerServiceTests/unit/Android.bp29
-rw-r--r--services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml27
-rw-r--r--services/tests/PackageManagerServiceTests/unit/AndroidTest.xml30
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt304
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt174
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt478
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt103
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt40
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt222
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt500
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt9
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java118
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ScanTests.java9
-rw-r--r--services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt35
74 files changed, 9400 insertions, 1562 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 731d1a91610c..4b67ec2138e6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -45795,6 +45795,8 @@ package android.util {
method public void clear();
method public android.util.SparseArray<E> clone();
method public boolean contains(int);
+ method public boolean contentEquals(@Nullable android.util.SparseArray<E>);
+ method public int contentHashCode();
method public void delete(int);
method public E get(int);
method public E get(int, E);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3a7571c8e48b..10423a63cb17 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -34,6 +34,7 @@ package android {
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
+ field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
@@ -86,6 +87,7 @@ package android {
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
+ field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -258,6 +260,7 @@ package android {
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
field public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+ field public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION = "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
field public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
field public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
@@ -2100,6 +2103,7 @@ package android.content {
field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
+ field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String FONT_SERVICE = "font";
@@ -2143,12 +2147,13 @@ package android.content {
field public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
field public static final String ACTION_DEVICE_CUSTOMIZATION_READY = "android.intent.action.DEVICE_CUSTOMIZATION_READY";
field public static final String ACTION_DIAL_EMERGENCY = "android.intent.action.DIAL_EMERGENCY";
+ field public static final String ACTION_DOMAINS_NEED_VERIFICATION = "android.intent.action.DOMAINS_NEED_VERIFICATION";
field public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
field public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
field public static final String ACTION_INCIDENT_REPORT_READY = "android.intent.action.INCIDENT_REPORT_READY";
field public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
- field public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+ field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
field public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
@@ -2481,8 +2486,8 @@ package android.content.pm {
method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent();
method @Nullable public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
- method @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+ method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -2506,9 +2511,9 @@ package android.content.pm {
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
method public void setSystemAppState(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
- method @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
- method @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -2576,13 +2581,13 @@ package android.content.pm {
field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99
field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a
field public static final int INSTALL_SUCCEEDED = 1; // 0x1
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
- field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
- field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
- field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
+ field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
+ field @Deprecated public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
+ field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
@@ -2710,6 +2715,52 @@ package android.content.pm.permission {
}
+package android.content.pm.verify.domain {
+
+ public final class DomainVerificationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+ method @NonNull public java.util.UUID getIdentifier();
+ method @NonNull public String getPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
+ }
+
+ public interface DomainVerificationManager {
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
+ method public static boolean isStateModifiable(int);
+ method public static boolean isStateVerified(int);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+ field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+ field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
+ field public static final int STATE_NO_RESPONSE = 0; // 0x0
+ field public static final int STATE_SUCCESS = 1; // 0x1
+ }
+
+ public final class DomainVerificationRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Set<java.lang.String> getPackageNames();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR;
+ }
+
+ public final class DomainVerificationUserSelection implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap();
+ method @NonNull public java.util.UUID getIdentifier();
+ method @NonNull public String getPackageName();
+ method @NonNull public android.os.UserHandle getUser();
+ method @NonNull public boolean isLinkHandlingAllowed();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
+ }
+
+}
+
package android.content.rollback {
public final class PackageRollbackInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f8c33b58b689..7404e53bd8b3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -69,6 +69,9 @@ import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.IDomainVerificationManager;
import android.content.res.Resources;
import android.content.rollback.RollbackManagerFrameworkInitializer;
import android.debug.AdbManager;
@@ -1388,6 +1391,20 @@ public final class SystemServiceRegistry {
}
});
+ // TODO(b/159952358): Only register this service for the domain verification agent?
+ registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class,
+ new CachedServiceFetcher<DomainVerificationManager>() {
+ @Override
+ public DomainVerificationManager createService(ContextImpl context)
+ throws ServiceNotFoundException {
+ IBinder binder = ServiceManager.getServiceOrThrow(
+ Context.DOMAIN_VERIFICATION_SERVICE);
+ IDomainVerificationManager service =
+ IDomainVerificationManager.Stub.asInterface(binder);
+ return new DomainVerificationManagerImpl(context, service);
+ }
+ });
+
sInitializing = true;
try {
// Note: the following functions need to be @SystemApis, once they become mainline
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4dc41b2d5114..987de3fca6b1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5451,6 +5451,15 @@ public abstract class Context {
public static final String GAME_SERVICE = "game";
/**
+ * Use with {@link #getSystemService(String)} to access domain verification service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ @SystemApi
+ public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1752b480c06b..30b24044a624 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -2841,10 +2842,28 @@ public class Intent implements Parcelable, Cloneable {
* </p>
*
* @hide
+ * @deprecated Superseded by domain verification APIs. See {@link DomainVerificationManager}.
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION =
+ "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+
+ /**
+ * Broadcast Action: Sent to the system domain verification agent when an app's domains need
+ * to be verified. The data contains the domains hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+ public static final String ACTION_DOMAINS_NEED_VERIFICATION =
+ "android.intent.action.DOMAINS_NEED_VERIFICATION";
/**
* Broadcast Action: Resources for a set of packages (which were
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 34d100354051..b8829bbf1ca5 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -627,9 +627,13 @@ interface IPackageManager {
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
+ /** @deprecated */
void verifyIntentFilter(int id, int verificationCode, in List<String> failedDomains);
+ /** @deprecated */
int getIntentVerificationStatus(String packageName, int userId);
+ /** @deprecated */
boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+ /** @deprecated */
ParceledListSlice getIntentFilterVerifications(String packageName);
ParceledListSlice getAllIntentFilters(String packageName);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0e7e6da7a4f1..d09d83f0cd1d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,6 +47,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.dex.ArtManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -93,6 +94,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
/**
* Class for retrieving various kinds of information related to the application
@@ -2201,8 +2203,10 @@ public abstract class PackageManager {
* {@link PackageManager#verifyIntentFilter} to indicate that the calling
* IntentFilter Verifier confirms that the IntentFilter is verified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
@@ -2211,16 +2215,20 @@ public abstract class PackageManager {
* {@link PackageManager#verifyIntentFilter} to indicate that the calling
* IntentFilter Verifier confirms that the IntentFilter is NOT verified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
/**
* Internal status code to indicate that an IntentFilter verification result is not specified.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
@@ -2230,8 +2238,10 @@ public abstract class PackageManager {
* will always be prompted the Intent Disambiguation Dialog if there are two
* or more Intent resolved for the IntentFilter's domain(s).
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
@@ -2242,8 +2252,10 @@ public abstract class PackageManager {
* or more resolution of the Intent. The default App for the domain(s)
* specified in the IntentFilter will also ALWAYS be used.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
@@ -2254,8 +2266,10 @@ public abstract class PackageManager {
* Intent resolved. The default App for the domain(s) specified in the
* IntentFilter will also NEVER be presented to the User.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
@@ -2268,8 +2282,10 @@ public abstract class PackageManager {
* more than one candidate app, then a disambiguation is *always* presented
* even if there is another candidate app with the 'always' state.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
@@ -3743,8 +3759,10 @@ public abstract class PackageManager {
* Passed to an intent filter verifier and is used to call back to
* {@link #verifyIntentFilter}
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
@@ -3754,8 +3772,10 @@ public abstract class PackageManager {
*
* Usually this is "https"
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
@@ -3766,8 +3786,10 @@ public abstract class PackageManager {
*
* This is a space delimited list of hosts.
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
@@ -3777,8 +3799,10 @@ public abstract class PackageManager {
* from the hosts. Each host response will need to include the package name of APK containing
* the intent filter.
*
+ * @deprecated Use DomainVerificationManager APIs.
* @hide
*/
+ @Deprecated
public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
= "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
@@ -6956,8 +6980,10 @@ public abstract class PackageManager {
* @throws SecurityException if the caller does not have the
* INTENT_FILTER_VERIFICATION_AGENT permission.
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT)
@@ -6982,8 +7008,10 @@ public abstract class PackageManager {
* {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
* {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
*
+ * @deprecated Use {@link DomainVerificationManager} APIs.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -7008,8 +7036,18 @@ public abstract class PackageManager {
*
* @return true if the status has been set. False otherwise.
*
+ * @deprecated This API represents a very dangerous behavior where Settings or a system app with
+ * the right permissions can force an application to be verified for all of its declared
+ * domains. This has been removed to prevent unintended usage, and no longer does anything,
+ * always returning false. If a caller truly wishes to grant <i></i>every</i> declared web
+ * domain to an application, use
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+ * passing in all of the domains returned inside
+ * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}.
+ *
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
@RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
@@ -7026,8 +7064,10 @@ public abstract class PackageManager {
*
* @return a list of IntentFilterVerificationInfo for a specific package.
*
+ * @deprecated Use {@link DomainVerificationManager} instead.
* @hide
*/
+ @Deprecated
@SuppressWarnings("HiddenAbstractMethod")
@NonNull
@SystemApi
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 99258712030c..5cc74c0a1c8e 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -77,8 +77,6 @@ public class PackageUserState {
public boolean virtualPreload;
public int enabled;
public String lastDisableAppCaller;
- public int domainVerificationStatus;
- public int appLinkGeneration;
public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
public int installReason;
public @PackageManager.UninstallReason int uninstallReason;
@@ -100,8 +98,6 @@ public class PackageUserState {
hidden = false;
suspended = false;
enabled = COMPONENT_ENABLED_STATE_DEFAULT;
- domainVerificationStatus =
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
installReason = PackageManager.INSTALL_REASON_UNKNOWN;
uninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
}
@@ -120,8 +116,6 @@ public class PackageUserState {
virtualPreload = o.virtualPreload;
enabled = o.enabled;
lastDisableAppCaller = o.lastDisableAppCaller;
- domainVerificationStatus = o.domainVerificationStatus;
- appLinkGeneration = o.appLinkGeneration;
categoryHint = o.categoryHint;
installReason = o.installReason;
uninstallReason = o.uninstallReason;
@@ -416,12 +410,6 @@ public class PackageUserState {
&& !lastDisableAppCaller.equals(oldState.lastDisableAppCaller))) {
return false;
}
- if (domainVerificationStatus != oldState.domainVerificationStatus) {
- return false;
- }
- if (appLinkGeneration != oldState.appLinkGeneration) {
- return false;
- }
if (categoryHint != oldState.categoryHint) {
return false;
}
@@ -481,8 +469,6 @@ public class PackageUserState {
hashCode = 31 * hashCode + Boolean.hashCode(virtualPreload);
hashCode = 31 * hashCode + enabled;
hashCode = 31 * hashCode + Objects.hashCode(lastDisableAppCaller);
- hashCode = 31 * hashCode + domainVerificationStatus;
- hashCode = 31 * hashCode + appLinkGeneration;
hashCode = 31 * hashCode + categoryHint;
hashCode = 31 * hashCode + installReason;
hashCode = 31 * hashCode + uninstallReason;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 8fbf2879bc27..31413b615697 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2493,6 +2493,10 @@ public class ParsingPackageUtils {
/**
* Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
+ *
+ * This is distinct from any of the functionality of app links domain verification, and cannot
+ * be converted to remain backwards compatible. It's possible the presence of this flag does
+ * not indicate a valid package for domain verification.
*/
private static boolean hasDomainURLs(ParsingPackage pkg) {
final List<ParsedActivity> activities = pkg.getActivities();
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
new file mode 100644
index 000000000000..c143cc517486
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+parcelable DomainVerificationInfo;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
new file mode 100644
index 000000000000..7afbe1fcb69f
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the state of all domains for a given package on device. Used by the domain verification
+ * agent to determine the domains declared by a package that need to be verified by comparing
+ * against the digital asset links response from the server hosting that domain.
+ * <p>
+ * These values for each domain can be modified through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true)
+public final class DomainVerificationInfo implements Parcelable {
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ */
+ @NonNull
+ private final Map<String, Integer> mHostToStateMap;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationInfo.
+ *
+ * @param identifier
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param hostToStateMap
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationInfo(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull Map<String,Integer> hostToStateMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * A domain verification ID for use in later API calls. This represents the snapshot of the
+ * domains for a package on device, and will be invalidated whenever the package changes.
+ * <p>
+ * An exception will be thrown at the next API call that receives the ID if it is no longer
+ * valid.
+ * <p>
+ * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+ * which point it can use the package name to evict existing requests with an invalid set ID. If
+ * the caller wants to manually check if any IDs have been invalidate, the {@link
+ * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+ * the last query of this method, prompting the caller to re-query.
+ * <p>
+ * This allows the caller to arbitrarily grant or revoke domain verification status, through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Map of host names to their current state. State is an integer, which defaults to
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+ * domain verification agent (the intended consumer of this API), which can be equal
+ * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+ * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+ * any unsuccessful response.
+ * <p>
+ * Any value non-inclusive between those 2 values are reserved for use by the system.
+ * The domain verification agent may be able to act on these reserved values, and this
+ * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+ * It is expected that the agent attempt to verify all domains that it can modify the
+ * state of, even if it does not understand the meaning of those values.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Integer> getHostToStateMap() {
+ return mHostToStateMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationInfo { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "hostToStateMap = " + mHostToStateMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationInfo that = (DomainVerificationInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ dest.writeMap(mHostToStateMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>();
+ in.readMap(hostToStateMap, Integer.class.getClassLoader());
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mHostToStateMap = hostToStateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToStateMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationInfo> CREATOR
+ = new Parcelable.Creator<DomainVerificationInfo>() {
+ @Override
+ public DomainVerificationInfo[] newArray(int size) {
+ return new DomainVerificationInfo[size];
+ }
+
+ @Override
+ public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611862790369L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
new file mode 100644
index 000000000000..af12536fff99
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.UserHandle;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * System service to access the domain verification APIs.
+ *
+ * Allows the approved domain verification
+ * agent on the device (the sole holder of
+ * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status
+ * of domains declared by applications in their AndroidManifest.xml, to allow them to open those
+ * links inside the app when selected by the user. This is done through querying
+ * {@link #getDomainVerificationInfo(String)} and calling
+ * {@link #setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * Also allows the domain preference settings (holder of
+ * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the
+ * preferences of the user, when they have chosen to explicitly allow an application to open links.
+ * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
+public interface DomainVerificationManager {
+
+ /**
+ * Extra field name for a {@link DomainVerificationRequest} for the requested packages.
+ * Passed to an the domain verification agent that handles
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+ */
+ String EXTRA_VERIFICATION_REQUEST =
+ "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+
+ /**
+ * No response has been recorded by either the system or any verification agent.
+ */
+ int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+
+ /** The verification agent has explicitly verified the domain at some point. */
+ int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+
+ /**
+ * The first available custom response code. This and any greater integer, along with
+ * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values
+ * will be treated as if the domain is unverified.
+ */
+ int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+ /** @hide */
+ @NonNull
+ static String stateToDebugString(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ return "none";
+ case DomainVerificationState.STATE_SUCCESS:
+ return "verified";
+ case DomainVerificationState.STATE_APPROVED:
+ return "approved";
+ case DomainVerificationState.STATE_DENIED:
+ return "denied";
+ case DomainVerificationState.STATE_MIGRATED:
+ return "migrated";
+ case DomainVerificationState.STATE_RESTORED:
+ return "restored";
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return "legacy_failure";
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return "system_configured";
+ default:
+ return String.valueOf(state);
+ }
+ }
+
+ /**
+ * Checks if a state considers the corresponding domain to be successfully verified. The
+ * domain verification agent may use this to determine whether or not to re-verify a domain.
+ */
+ static boolean isStateVerified(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return true;
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state is modifiable by the domain verification agent. This is useful as the
+ * platform may add new state codes in newer versions, and older verification agents can use
+ * this method to determine if a state can be changed without having to be aware of what the
+ * new state means.
+ */
+ static boolean isStateModifiable(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return true;
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return false;
+ default:
+ return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ }
+ }
+
+ /**
+ * For determine re-verify policy. This is hidden from the domain verification agent so that
+ * no behavior is made based on the result.
+ * @hide
+ */
+ static boolean isStateDefault(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ return true;
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
+ * usually a heavy workload and should be done infrequently.
+ *
+ * @return the current snapshot of package names with valid autoVerify URLs.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ List<String> getValidVerificationPackageNames();
+
+ /**
+ * Retrieves the domain verification state for a given package.
+ *
+ * @return the data for the package, or null if it does not declare any autoVerify domains
+ * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
+ * and should not be re-tried except on a time scheduled basis.
+ */
+ @Nullable
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ })
+ DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Change the verification status of the {@param domains} of the package associated with
+ * {@param domainSetId}.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains List of host names to change the state of.
+ * @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
+ * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+ * invalid. This usually means the work being processed by the
+ * verification agent is outdated and a new request should
+ * be scheduled, if one has not already been done as part of
+ * the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}
+ * broadcast.
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is
+ * installed on a volume that is no longer mounted. This
+ * error is unrecoverable until the package is available
+ * again, and should not be re-tried except on a time
+ * scheduled basis.
+ */
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ @DomainVerificationState.State int state) throws NameNotFoundException;
+
+ /**
+ * TODO(b/178525735): This documentation is incorrect in the context of UX changes.
+ * Change whether the given {@param packageName} is allowed to automatically open verified
+ * HTTP/HTTPS domains. The final state is determined along with the verification status for the
+ * specific domain being opened and other system state. An app with this enabled is not
+ * guaranteed to be the sole link handler for its domains.
+ *
+ * By default, all apps are allowed to open verified links. Users must disable them explicitly.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed)
+ throws NameNotFoundException;
+
+ /**
+ * Update the recorded user selection for the given {@param domains} for the given {@param
+ * domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
+ * and will never be reset by the system short of an app data clear.
+ *
+ * This state is stored per device user. If another user needs to be changed, the appropriate
+ * permissions must be acquired and
+ * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+ *
+ * This will be combined with the verification status and other system state to determine which
+ * application is launched to handle an app link.
+ *
+ * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+ * @param domains The domains to toggle the state of.
+ * @param enabled Whether or not the app should automatically open the domains specified.
+ * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+ * invalid.
+ * @throws NameNotFoundException If the ID is known to be good, but the package is
+ * unavailable. This may be because the package is
+ * installed on a volume that is no longer mounted. This
+ * error is unrecoverable until the package is available
+ * again, and should not be re-tried except on a time
+ * scheduled basis.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException;
+
+ /**
+ * Retrieve the user selection data for the given {@param packageName} and the current user.
+ * It is the responsibility of the caller to ensure that the
+ * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls.
+ *
+ * This state is stored per device user. If another user needs to be accessed, the appropriate
+ * permissions must be acquired and
+ * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+ *
+ * @param packageName The app to query state for.
+ * @return the user selection verification data for the given package for the current user,
+ * or null if the package does not declare any HTTP/HTTPS domains.
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
+ throws NameNotFoundException;
+
+ /**
+ * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
+ * provided by the caller is no longer valid. This may be recoverable, and the caller should
+ * re-query the package name associated with the ID using
+ * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the
+ * package is no longer known to the device and thus all pending work for it should be dropped.
+ *
+ * @hide
+ */
+ class InvalidDomainSetException extends IllegalArgumentException {
+
+ public static final int REASON_ID_NULL = 1;
+ public static final int REASON_ID_INVALID = 2;
+ public static final int REASON_SET_NULL_OR_EMPTY = 3;
+ public static final int REASON_UNKNOWN_DOMAIN = 4;
+
+ /** @hide */
+ @IntDef({
+ REASON_ID_NULL,
+ REASON_ID_INVALID,
+ REASON_SET_NULL_OR_EMPTY,
+ REASON_UNKNOWN_DOMAIN
+ })
+ public @interface Reason {
+ }
+
+ public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName,
+ @Reason int reason) {
+ switch (reason) {
+ case REASON_ID_NULL:
+ return "Domain set ID cannot be null";
+ case REASON_ID_INVALID:
+ return "Domain set ID " + domainSetId + " has been invalidated";
+ case REASON_SET_NULL_OR_EMPTY:
+ return "Domain set cannot be null or empty";
+ case REASON_UNKNOWN_DOMAIN:
+ return "Domain set contains value that was not declared by the target package "
+ + packageName;
+ default:
+ return "Unknown failure";
+ }
+ }
+
+ @Reason
+ private final int mReason;
+
+ @Nullable
+ private final UUID mDomainSetId;
+
+ @Nullable
+ private final String mPackageName;
+
+ /** @hide */
+ public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
+ @Reason int reason) {
+ super(buildMessage(domainSetId, packageName, reason));
+ mDomainSetId = domainSetId;
+ mPackageName = packageName;
+ mReason = reason;
+ }
+
+ @Nullable
+ public UUID getDomainSetId() {
+ return mDomainSetId;
+ }
+
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Reason
+ public int getReason() {
+ return mReason;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
new file mode 100644
index 000000000000..5938def5c83c
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+@SuppressWarnings("RedundantThrows")
+public class DomainVerificationManagerImpl implements DomainVerificationManager {
+
+ public static final int ERROR_INVALID_DOMAIN_SET = 1;
+ public static final int ERROR_NAME_NOT_FOUND = 2;
+
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_DOMAIN_SET,
+ ERROR_NAME_NOT_FOUND,
+ })
+ private @interface Error {
+ }
+
+ private final Context mContext;
+
+ private final IDomainVerificationManager mDomainVerificationManager;
+
+ public DomainVerificationManagerImpl(Context context,
+ IDomainVerificationManager domainVerificationManager) {
+ mContext = context;
+ mDomainVerificationManager = domainVerificationManager;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ try {
+ return mDomainVerificationManager.getValidVerificationPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws IllegalArgumentException, NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+ new ArrayList<>(domains), state);
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+ allowed, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled)
+ throws IllegalArgumentException, NameNotFoundException {
+ try {
+ mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
+ new ArrayList<>(domains), enabled, mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, domainSetId);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName) throws NameNotFoundException {
+ try {
+ return mDomainVerificationManager.getDomainVerificationUserSelection(packageName,
+ mContext.getUserId());
+ } catch (Exception e) {
+ Exception converted = rethrow(e, packageName);
+ if (converted instanceof NameNotFoundException) {
+ throw (NameNotFoundException) converted;
+ } else if (converted instanceof RuntimeException) {
+ throw (RuntimeException) converted;
+ } else {
+ throw new RuntimeException(converted);
+ }
+ }
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
+ return rethrow(exception, domainSetId, null);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable String packageName) {
+ return rethrow(exception, null, packageName);
+ }
+
+ private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
+ @Nullable String packageName) {
+ if (exception instanceof ServiceSpecificException) {
+ int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+ if (packageName == null) {
+ packageName = exception.getMessage();
+ }
+
+ @Error int managerErrorCode = packedErrorCode & 0xFFFF;
+ switch (managerErrorCode) {
+ case ERROR_INVALID_DOMAIN_SET:
+ int errorSpecificCode = packedErrorCode >> 16;
+ return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
+ domainSetId, packageName, errorSpecificCode));
+ case ERROR_NAME_NOT_FOUND:
+ return new NameNotFoundException(packageName);
+ default:
+ return exception;
+ }
+ } else if (exception instanceof RemoteException) {
+ return ((RemoteException) exception).rethrowFromSystemServer();
+ } else {
+ return exception;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
new file mode 100644
index 000000000000..473abce26d81
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/**
+ * Request object sent in the {@link Intent} that's broadcast to the domain verification
+ * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
+ * <p>
+ * This contains the set of packages which have been invalidated and will require
+ * re-verification. The exact domains can be retrieved with
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)}
+ *
+ * @hide
+ */
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genHiddenConstructor = true, genAidl = false, genEqualsHashCode = true)
+@SystemApi
+public final class DomainVerificationRequest implements Parcelable {
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class)
+ private final Set<String> mPackageNames;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationRequest.
+ *
+ * @param packageNames
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationRequest(
+ @NonNull Set<String> packageNames) {
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The package names of the apps that need to be verified. The receiver should call
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+ * these values to get the actual set of domains that need to be acted on.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getPackageNames() {
+ return mPackageNames;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationRequest that = (DomainVerificationRequest) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageNames, that.mPackageNames);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageNames);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<Set<String>> sParcellingForPackageNames =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForStringSet.class);
+ static {
+ if (sParcellingForPackageNames == null) {
+ sParcellingForPackageNames = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForStringSet());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ sParcellingForPackageNames.parcel(mPackageNames, dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<String> packageNames = sParcellingForPackageNames.unparcel(in);
+
+ this.mPackageNames = packageNames;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageNames);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationRequest> CREATOR
+ = new Parcelable.Creator<DomainVerificationRequest>() {
+ @Override
+ public DomainVerificationRequest[] newArray(int size) {
+ return new DomainVerificationRequest[size];
+ }
+
+ @Override
+ public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611862814990L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
new file mode 100644
index 000000000000..17593ef2aeb1
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
@@ -0,0 +1,100 @@
+/*
+ * 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+
+/**
+ * @hide
+ */
+public interface DomainVerificationState {
+
+ /**
+ * @hide
+ */
+ @IntDef({
+ STATE_NO_RESPONSE,
+ STATE_SUCCESS,
+ STATE_MIGRATED,
+ STATE_RESTORED,
+ STATE_APPROVED,
+ STATE_DENIED,
+ STATE_LEGACY_FAILURE,
+ STATE_SYS_CONFIG,
+ STATE_FIRST_VERIFIER_DEFINED
+ })
+ @interface State {
+ }
+
+ // TODO(b/159952358): Document all the places that states need to be updated when one is added
+ /**
+ * @see DomainVerificationManager#STATE_NO_RESPONSE
+ */
+ int STATE_NO_RESPONSE = 0;
+
+ /**
+ * @see DomainVerificationManager#STATE_SUCCESS
+ */
+ int STATE_SUCCESS = 1;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as verified.
+ */
+ int STATE_APPROVED = 2;
+
+ /**
+ * The system has chosen to ignore the verification agent's opinion on whether the domain should
+ * be verified. This will treat the domain as unverified.
+ */
+ int STATE_DENIED = 3;
+
+ /**
+ * The state was migrated from the previous intent filter verification API. This will treat the
+ * domain as verified, but it should be updated by the verification agent. The older API's
+ * collection and handling of verifying domains may lead to improperly migrated state.
+ */
+ int STATE_MIGRATED = 4;
+
+ /**
+ * The state was restored from a user backup or by the system. This is treated as if the domain
+ * was verified, but the verification agent may choose to re-verify this domain to be certain
+ * nothing has changed since the snapshot.
+ */
+ int STATE_RESTORED = 5;
+
+ /**
+ * The domain was failed by a legacy intent filter verification agent from v1 of the API. This
+ * is made distinct from {@link #STATE_FIRST_VERIFIER_DEFINED} to prevent any v2 verification
+ * agent from misinterpreting the result, since {@link #STATE_FIRST_VERIFIER_DEFINED} is agent
+ * specific and can be defined as a special error code.
+ */
+ int STATE_LEGACY_FAILURE = 6;
+
+ /**
+ * The application has been granted auto verification for all domains by configuration on the
+ * system image.
+ *
+ * TODO: Can be stored per-package rather than for all domains for a package to save memory.
+ */
+ int STATE_SYS_CONFIG = 7;
+
+ /**
+ * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED
+ */
+ int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
new file mode 100644
index 000000000000..ddb5ef85382a
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+parcelable DomainVerificationUserSelection;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
new file mode 100644
index 000000000000..8d16f75bf1b4
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the user selection state for a package. This means all web HTTP(S) domains
+ * declared by a package in its manifest, whether or not they were marked for auto
+ * verification.
+ * <p>
+ * By default, all apps are allowed to automatically open links with domains that they've
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening these
+ * links.
+ * <p>
+ * Separately, independent of this toggle, the user can choose specific domains to allow
+ * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
+ * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * <p>
+ * These values can be changed through the
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
+ * boolean)} and
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)} APIs.
+ * <p>
+ * Note that because state is per user, if a different user needs to be changed, one will
+ * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+ genEqualsHashCode = true)
+public final class DomainVerificationUserSelection implements Parcelable {
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ */
+ @NonNull
+ @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+ private final UUID mIdentifier;
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @NonNull
+ private final UserHandle mUser;
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @NonNull
+ private final boolean mLinkHandlingAllowed;
+
+ /**
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ *
+ * @return Map of hosts to enabled state for the given package and user.
+ */
+ @NonNull
+ private final Map<String, Boolean> mHostToUserSelectionMap;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationUserSelection.
+ *
+ * @param packageName
+ * The package name that this data corresponds to.
+ * @param user
+ * The user that this data corresponds to.
+ * @param linkHandlingAllowed
+ * Whether or not this package is allowed to open links.
+ * @param hostToUserSelectionMap
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationUserSelection(
+ @NonNull UUID identifier,
+ @NonNull String packageName,
+ @NonNull UserHandle user,
+ @NonNull boolean linkHandlingAllowed,
+ @NonNull Map<String,Boolean> hostToUserSelectionMap) {
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToUserSelectionMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * @see DomainVerificationInfo#getIdentifier
+ */
+ @DataClass.Generated.Member
+ public @NonNull UUID getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * The package name that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * The user that this data corresponds to.
+ */
+ @DataClass.Generated.Member
+ public @NonNull UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Whether or not this package is allowed to open links.
+ */
+ @DataClass.Generated.Member
+ public @NonNull boolean isLinkHandlingAllowed() {
+ return mLinkHandlingAllowed;
+ }
+
+ /**
+ * Retrieve the existing user selection state for the matching
+ * {@link #getPackageName()}, as was previously set by
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)}.
+ *
+ * @return Map of hosts to enabled state for the given package and user.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,Boolean> getHostToUserSelectionMap() {
+ return mHostToUserSelectionMap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationUserSelection { " +
+ "identifier = " + mIdentifier + ", " +
+ "packageName = " + mPackageName + ", " +
+ "user = " + mUser + ", " +
+ "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
+ "hostToUserSelectionMap = " + mHostToUserSelectionMap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationUserSelection that = (DomainVerificationUserSelection) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mUser, that.mUser)
+ && mLinkHandlingAllowed == that.mLinkHandlingAllowed
+ && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mUser);
+ _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap);
+ return _hash;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<UUID> sParcellingForIdentifier =
+ Parcelling.Cache.get(
+ Parcelling.BuiltIn.ForUUID.class);
+ static {
+ if (sParcellingForIdentifier == null) {
+ sParcellingForIdentifier = Parcelling.Cache.put(
+ new Parcelling.BuiltIn.ForUUID());
+ }
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLinkHandlingAllowed) flg |= 0x8;
+ dest.writeByte(flg);
+ sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+ dest.writeString(mPackageName);
+ dest.writeTypedObject(mUser, flags);
+ dest.writeMap(mHostToUserSelectionMap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean linkHandlingAllowed = (flg & 0x8) != 0;
+ UUID identifier = sParcellingForIdentifier.unparcel(in);
+ String packageName = in.readString();
+ UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>();
+ in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader());
+
+ this.mIdentifier = identifier;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIdentifier);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUser = user;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUser);
+ this.mLinkHandlingAllowed = linkHandlingAllowed;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLinkHandlingAllowed);
+ this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostToUserSelectionMap);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR
+ = new Parcelable.Creator<DomainVerificationUserSelection>() {
+ @Override
+ public DomainVerificationUserSelection[] newArray(int size) {
+ return new DomainVerificationUserSelection[size];
+ }
+
+ @Override
+ public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainVerificationUserSelection(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1611799495498L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
new file mode 100644
index 000000000000..21dd623b46bc
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020, 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 android.content.pm.verify.domain;
+
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import java.util.List;
+
+/**
+ * @see DomainVerificationManager
+ * @hide
+ */
+interface IDomainVerificationManager {
+
+ List<String> getValidVerificationPackageNames();
+
+ @nullable
+ DomainVerificationInfo getDomainVerificationInfo(String packageName);
+
+ @nullable
+ DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
+ int userId);
+
+ void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state);
+
+ void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
+
+ void setDomainVerificationUserSelection(String domainSetId, in List<String> domains,
+ boolean enabled, int userId);
+}
diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 000000000000..c6c979107e75
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 86120d1e650c..6718e93f908c 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -16,6 +16,7 @@
package android.util;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
@@ -23,6 +24,8 @@ import com.android.internal.util.GrowingArrayUtils;
import libcore.util.EmptyArray;
+import java.util.Objects;
+
/**
* <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects,
* its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient
@@ -505,4 +508,44 @@ public class SparseArray<E> implements Cloneable {
buffer.append('}');
return buffer.toString();
}
+
+ /**
+ * For backwards compatibility reasons, {@link Object#equals(Object)} cannot be implemented,
+ * so this serves as a manually invoked alternative.
+ */
+ public boolean contentEquals(@Nullable SparseArray<E> other) {
+ if (other == null) {
+ return false;
+ }
+
+ int size = size();
+ if (size != other.size()) {
+ return false;
+ }
+
+ for (int index = 0; index < size; index++) {
+ int key = keyAt(index);
+ if (!Objects.equals(valueAt(index), other.get(key))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * For backwards compatibility, {@link Object#hashCode()} cannot be implemented, so this serves
+ * as a manually invoked alternative.
+ */
+ public int contentHashCode() {
+ int hash = 0;
+ int size = size();
+ for (int index = 0; index < size; index++) {
+ int key = keyAt(index);
+ E value = valueAt(index);
+ hash = 31 * hash + Objects.hashCode(key);
+ hash = 31 * hash + Objects.hashCode(value);
+ }
+ return hash;
+ }
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 3cf00aed3880..c6fd6eec2f91 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -35,6 +35,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.IntFunction;
/**
@@ -599,6 +600,20 @@ public class ArrayUtils {
return cur;
}
+ /**
+ * Similar to {@link Set#addAll(Collection)}}, but with support for set values of {@code null}.
+ */
+ public static @NonNull <T> ArraySet<T> addAll(@Nullable ArraySet<T> cur,
+ @Nullable Collection<T> val) {
+ if (cur == null) {
+ cur = new ArraySet<>();
+ }
+ if (val != null) {
+ cur.addAll(val);
+ }
+ return cur;
+ }
+
public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) {
if (cur == null) {
return null;
diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java
index dd64c402fdbf..1ab316d07e42 100644
--- a/core/java/com/android/internal/util/Parcelling.java
+++ b/core/java/com/android/internal/util/Parcelling.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.regex.Pattern;
/**
@@ -291,5 +292,19 @@ public interface Parcelling<T> {
return s == null ? null : Pattern.compile(s);
}
}
+
+ class ForUUID implements Parcelling<UUID> {
+
+ @Override
+ public void parcel(UUID item, Parcel dest, int parcelFlags) {
+ dest.writeString(item == null ? null : item.toString());
+ }
+
+ @Override
+ public UUID unparcel(Parcel source) {
+ String string = source.readString();
+ return string == null ? null : UUID.fromString(string);
+ }
+ }
}
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index e5bc47097751..cb586d660634 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -178,8 +178,9 @@ public class SystemConfig {
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
- // These are the package names of apps which should be in the 'always'
- // URL-handling state upon factory reset.
+ // These are the package names of apps which should be automatically granted domain verification
+ // for all of their domains. The only way these apps can be overridden by the user is by
+ // explicitly disabling overall link handling support in app info.
final ArraySet<String> mLinkedApps = new ArraySet<>();
// These are the components that are enabled by default as VR mode listener services.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5442c0355590..3bff755c63c6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -93,6 +93,7 @@
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
<protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
<protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.DOMAINS_NEED_VERIFICATION" />
<protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
<protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
<protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
@@ -4702,6 +4703,26 @@
<permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
+ system will trust it to verify domains.
+
+ TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
+ -->
+ <permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Must be required by the domain verification agent's intent
+ BroadcastReceiver, to ensure that only the system can interact with it.
+ -->
+ <permission android:name="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an app like Settings to update the user's grants to what domains
+ an app is allowed to automatically open.
+ -->
+ <permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to access serial ports via the SerialManager.
@hide -->
<permission android:name="android.permission.SERIAL_PORT"
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 4f986bd5276b..2a1fc87fc29f 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -33,7 +33,7 @@ public final class DumpState {
public static final int DUMP_KEYSETS = 1 << 14;
public static final int DUMP_VERSION = 1 << 15;
public static final int DUMP_INSTALLS = 1 << 16;
- public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+ public static final int DUMP_DOMAIN_VERIFIER = 1 << 17;
public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
public static final int DUMP_FROZEN = 1 << 19;
public static final int DUMP_DEXOPT = 1 << 20;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6e5bd947c456..a702f5ea3582 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -377,6 +377,11 @@ import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.dex.ViewCompiler;
+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;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
@@ -399,6 +404,7 @@ import com.android.server.utils.Watchable;
import com.android.server.utils.Watched;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedSparseBooleanArray;
+import com.android.server.utils.WatchedSparseIntArray;
import com.android.server.utils.Watcher;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -460,6 +466,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.function.Supplier;
/**
* Keep track of all those APKs everywhere.
@@ -1063,6 +1070,9 @@ public class PackageManagerService extends IPackageManager.Stub
private final ServiceProducer mGetLocalServiceProducer;
private final ServiceProducer mGetSystemServiceProducer;
private final Singleton<ModuleInfoProvider> mModuleInfoProviderProducer;
+ private final Singleton<DomainVerificationManagerInternal>
+ mDomainVerificationManagerInternalProducer;
+ private final Singleton<Handler> mHandlerProducer;
Injector(Context context, Object lock, Installer installer,
Object installLock, PackageAbiHelper abiHelper,
@@ -1091,6 +1101,9 @@ public class PackageManagerService extends IPackageManager.Stub
instantAppResolverConnectionProducer,
Producer<ModuleInfoProvider> moduleInfoProviderProducer,
Producer<LegacyPermissionManagerInternal> legacyPermissionManagerInternalProducer,
+ Producer<DomainVerificationManagerInternal>
+ domainVerificationManagerInternalProducer,
+ Producer<Handler> handlerProducer,
SystemWrapper systemWrapper,
ServiceProducer getLocalServiceProducer,
ServiceProducer getSystemServiceProducer) {
@@ -1128,6 +1141,9 @@ public class PackageManagerService extends IPackageManager.Stub
mSystemWrapper = systemWrapper;
mGetLocalServiceProducer = getLocalServiceProducer;
mGetSystemServiceProducer = getSystemServiceProducer;
+ mDomainVerificationManagerInternalProducer =
+ new Singleton<>(domainVerificationManagerInternalProducer);
+ mHandlerProducer = new Singleton<>(handlerProducer);
}
/**
@@ -1273,6 +1289,14 @@ public class PackageManagerService extends IPackageManager.Stub
public LegacyPermissionManagerInternal getLegacyPermissionManagerInternal() {
return mLegacyPermissionManagerInternalProducer.get(this, mPackageManager);
}
+
+ public DomainVerificationManagerInternal getDomainVerificationManagerInternal() {
+ return mDomainVerificationManagerInternalProducer.get(this, mPackageManager);
+ }
+
+ public Handler getHandler() {
+ return mHandlerProducer.get(this, mPackageManager);
+ }
}
/** Provides an abstraction to static access to system state. */
@@ -1327,8 +1351,6 @@ public class PackageManagerService extends IPackageManager.Stub
public InstantAppRegistry instantAppRegistry;
public InstantAppResolverConnection instantAppResolverConnection;
public ComponentName instantAppResolverSettingsComponent;
- public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier;
- public @Nullable ComponentName intentFilterVerifierComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreNupgrade;
public boolean isPreQupgrade;
@@ -1451,10 +1473,8 @@ public class PackageManagerService extends IPackageManager.Stub
boolean mResolverReplaced = false;
- private final @Nullable ComponentName mIntentFilterVerifierComponent;
- private final @Nullable IntentFilterVerifier<ParsedIntentInfo> mIntentFilterVerifier;
-
- private int mIntentFilterVerificationToken = 0;
+ @NonNull
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
/** The service connection to the ephemeral resolver */
final InstantAppResolverConnection mInstantAppResolverConnection;
@@ -1470,9 +1490,6 @@ public class PackageManagerService extends IPackageManager.Stub
private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
- final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
- = new SparseArray<>();
-
// Internal interface for permission manager
private final PermissionManagerServiceInternal mPermissionManager;
@@ -1492,262 +1509,6 @@ public class PackageManagerService extends IPackageManager.Stub
private final PackageProperty mPackageProperty = new PackageProperty();
- private static class IFVerificationParams {
- String packageName;
- boolean hasDomainUrls;
- List<ParsedActivity> activities;
- boolean replacing;
- int userId;
- int verifierUid;
-
- public IFVerificationParams(String packageName, boolean hasDomainUrls,
- List<ParsedActivity> activities, boolean _replacing,
- int _userId, int _verifierUid) {
- this.packageName = packageName;
- this.hasDomainUrls = hasDomainUrls;
- this.activities = activities;
- replacing = _replacing;
- userId = _userId;
- verifierUid = _verifierUid;
- }
- }
-
- private interface IntentFilterVerifier<T extends IntentFilter> {
- boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
- T filter, String packageName);
- void startVerifications(int userId);
- void receiveVerificationResponse(int verificationId);
- }
-
- private class IntentVerifierProxy implements IntentFilterVerifier<ParsedIntentInfo> {
- private Context mContext;
- private ComponentName mIntentFilterVerifierComponent;
- private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<>();
-
- public IntentVerifierProxy(Context context, ComponentName verifierComponent) {
- mContext = context;
- mIntentFilterVerifierComponent = verifierComponent;
- }
-
- private String getDefaultScheme() {
- return IntentFilter.SCHEME_HTTPS;
- }
-
- @Override
- public void startVerifications(int userId) {
- // Launch verifications requests
- int count = mCurrentIntentFilterVerifications.size();
- for (int n=0; n<count; n++) {
- int verificationId = mCurrentIntentFilterVerifications.get(n);
- final IntentFilterVerificationState ivs =
- mIntentFilterVerificationStates.get(verificationId);
-
- String packageName = ivs.getPackageName();
-
- ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
- final int filterCount = filters.size();
- ArraySet<String> domainsSet = new ArraySet<>();
- for (int m=0; m<filterCount; m++) {
- ParsedIntentInfo filter = filters.get(m);
- domainsSet.addAll(filter.getHostsList());
- }
- synchronized (mLock) {
- if (mSettings.createIntentFilterVerificationIfNeededLPw(
- packageName, domainsSet) != null) {
- scheduleWriteSettingsLocked();
- }
- }
- sendVerificationRequest(verificationId, ivs);
- }
- mCurrentIntentFilterVerifications.clear();
- }
-
- private void sendVerificationRequest(int verificationId, IntentFilterVerificationState ivs) {
- Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
- verificationId);
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
- getDefaultScheme());
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
- ivs.getHostsString());
- verificationIntent.putExtra(
- PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
- ivs.getPackageName());
- verificationIntent.setComponent(mIntentFilterVerifierComponent);
- verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
- final long whitelistTimeout = getVerificationTimeout();
- final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setTemporaryAppWhitelistDuration(whitelistTimeout);
-
- DeviceIdleInternal idleController =
- mInjector.getLocalService(DeviceIdleInternal.class);
- idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
- mIntentFilterVerifierComponent.getPackageName(), whitelistTimeout,
- UserHandle.USER_SYSTEM, true, "intent filter verifier");
-
- mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM,
- null, options.toBundle());
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Sending IntentFilter verification broadcast");
- }
-
- public void receiveVerificationResponse(int verificationId) {
- IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
-
- final boolean verified = ivs.isVerified();
-
- ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
- final int count = filters.size();
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Received verification response " + verificationId
- + " for " + count + " filters, verified=" + verified);
- }
- for (int n=0; n<count; n++) {
- ParsedIntentInfo filter = filters.get(n);
- filter.setVerified(verified);
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "IntentFilter " + filter.toString()
- + " verified with result:" + verified + " and hosts:"
- + ivs.getHostsString());
- }
-
- mIntentFilterVerificationStates.remove(verificationId);
-
- final String packageName = ivs.getPackageName();
- IntentFilterVerificationInfo ivi;
-
- synchronized (mLock) {
- ivi = mSettings.getIntentFilterVerificationLPr(packageName);
- }
- if (ivi == null) {
- Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:"
- + verificationId + " packageName:" + packageName);
- return;
- }
-
- synchronized (mLock) {
- if (verified) {
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
- } else {
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
- }
- scheduleWriteSettingsLocked();
-
- final int userId = ivs.getUserId();
- if (userId != UserHandle.USER_ALL) {
- final int userStatus =
- mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
-
- int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- boolean needUpdate = false;
-
- // In a success case, we promote from undefined or ASK to ALWAYS. This
- // supports a flow where the app fails validation but then ships an updated
- // APK that passes, and therefore deserves to be in ALWAYS.
- //
- // If validation failed, the undefined state winds up in the basic ASK behavior,
- // but apps that previously passed and became ALWAYS are *demoted* out of
- // that state, since they would not deserve the ALWAYS behavior in case of a
- // clean install.
- switch (userStatus) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- if (!verified) {
- // Don't demote if sysconfig says 'always'
- SystemConfig systemConfig = mInjector.getSystemConfig();
- ArraySet<String> packages = systemConfig.getLinkedApps();
- if (!packages.contains(packageName)) {
- // updatedStatus is already UNDEFINED
- needUpdate = true;
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Formerly validated but now failing; demoting");
- }
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Updating bundled package " + packageName
- + " failed autoVerify, but sysconfig supersedes");
- }
- // leave needUpdate == false here intentionally
- }
- }
- break;
-
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
- // Stay in 'undefined' on verification failure
- if (verified) {
- updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- }
- needUpdate = true;
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Applying update; old=" + userStatus
- + " new=" + updatedStatus);
- }
- break;
-
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
- // Keep in 'ask' on failure
- if (verified) {
- updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- needUpdate = true;
- }
- break;
-
- default:
- // Nothing to do
- }
-
- if (needUpdate) {
- mSettings.updateIntentFilterVerificationStatusLPw(
- packageName, updatedStatus, userId);
- scheduleWritePackageRestrictionsLocked(userId);
- }
- } else {
- Slog.i(TAG, "autoVerify ignored when installing for all users");
- }
- }
- }
-
- @Override
- public boolean addOneIntentFilterVerification(int verifierUid, int userId, int verificationId,
- ParsedIntentInfo filter, String packageName) {
- if (!hasValidDomains(filter)) {
- return false;
- }
- IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
- if (ivs == null) {
- ivs = createDomainVerificationState(verifierUid, userId, verificationId,
- packageName);
- }
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Adding verification filter for " + packageName + ": " + filter);
- }
- ivs.addFilter(filter);
- return true;
- }
-
- private IntentFilterVerificationState createDomainVerificationState(int verifierUid,
- int userId, int verificationId, String packageName) {
- IntentFilterVerificationState ivs = new IntentFilterVerificationState(
- verifierUid, userId, packageName);
- ivs.setPendingState();
- synchronized (mLock) {
- mIntentFilterVerificationStates.append(verificationId, ivs);
- mCurrentIntentFilterVerifications.add(verificationId);
- }
- return ivs;
- }
- }
-
- private static boolean hasValidDomains(ParsedIntentInfo filter) {
- return filter.hasCategory(Intent.CATEGORY_BROWSABLE)
- && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
- filter.hasDataScheme(IntentFilter.SCHEME_HTTPS));
- }
-
// Set of pending broadcasts for aggregating enable/disable of components.
@VisibleForTesting(visibility = Visibility.PACKAGE)
public static class PendingPackageBroadcasts {
@@ -1822,8 +1583,8 @@ public class PackageManagerService extends IPackageManager.Stub
static final int WRITE_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
static final int CHECK_PENDING_VERIFICATION = 16;
- static final int START_INTENT_FILTER_VERIFICATIONS = 17;
- static final int INTENT_FILTER_VERIFIED = 18;
+ // public static final int UNUSED = 17;
+ // public static final int UNUSED = 18;
static final int WRITE_PACKAGE_LIST = 19;
static final int INSTANT_APP_RESOLUTION_PHASE_TWO = 20;
static final int ENABLE_ROLLBACK_STATUS = 21;
@@ -1832,6 +1593,7 @@ public class PackageManagerService extends IPackageManager.Stub
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
+ static final int DOMAIN_VERIFICATION = 27;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1925,6 +1687,74 @@ public class PackageManagerService extends IPackageManager.Stub
private final PackageUsage mPackageUsage = new PackageUsage();
private final CompilerStats mCompilerStats = new CompilerStats();
+ private final DomainVerificationConnection mDomainVerificationConnection =
+ new DomainVerificationConnection();
+
+ private class DomainVerificationConnection implements
+ DomainVerificationService.Connection, DomainVerificationProxyV1.Connection,
+ DomainVerificationProxyV2.Connection {
+
+ @Override
+ public void scheduleWriteSettings() {
+ synchronized (mLock) {
+ PackageManagerService.this.scheduleWriteSettingsLocked();
+ }
+ }
+
+ @Override
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ @UserIdInt
+ @Override
+ public int getCallingUserId() {
+ return UserHandle.getCallingUserId();
+ }
+
+ @Override
+ public void schedule(int code, @Nullable Object object) {
+ Message message = mHandler.obtainMessage(DOMAIN_VERIFICATION);
+ message.arg1 = code;
+ message.obj = object;
+ mHandler.sendMessage(message);
+ }
+
+ @Override
+ public long getPowerSaveTempWhitelistAppDuration() {
+ return PackageManagerService.this.getVerificationTimeout();
+ }
+
+ @Override
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return mInjector.getLocalService(DeviceIdleInternal.class);
+ }
+
+ @Override
+ public boolean isCallerPackage(int callingUid, @NonNull String packageName) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ 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);
+ }
+ }
+
/**
* Invalidate the package info cache, which includes updating the cached computer.
* @hide
@@ -2145,7 +1975,6 @@ public class PackageManagerService extends IPackageManager.Stub
boolean isImplicitImageCaptureIntentAndNotSetByDpc);
int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc);
- long getDomainVerificationStatusLPr(PackageSetting ps, int userId);
void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message);
void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
@@ -2199,6 +2028,7 @@ public class PackageManagerService extends IPackageManager.Stub
private final ComponentResolver mComponentResolver;
private final InstantAppResolverConnection mInstantAppResolverConnection;
private final DefaultAppProvider mDefaultAppProvider;
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
// PackageManagerService attributes that are primitives are referenced through the
// pms object directly. Primitives are the only attributes so referenced.
@@ -2244,6 +2074,7 @@ public class PackageManagerService extends IPackageManager.Stub
mComponentResolver = args.service.mComponentResolver;
mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
mDefaultAppProvider = args.service.mDefaultAppProvider;
+ mDomainVerificationManager = args.service.mDomainVerificationManager;
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
@@ -2755,8 +2586,6 @@ public class PackageManagerService extends IPackageManager.Stub
final ArrayList<ResolveInfo> result = new ArrayList<>();
final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
- final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>();
- final ArrayList<ResolveInfo> neverList = new ArrayList<>();
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
final int count = candidates.size();
// First, try to use linked apps. Partition the candidates into four lists:
@@ -2772,43 +2601,15 @@ public class PackageManagerService extends IPackageManager.Stub
matchAllList.add(info);
continue;
}
- // Try to get the status from User settings first
- long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- int status = (int)(packedStatus >> 32);
- int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + always: " + info.activityInfo.packageName
- + " : linkgen=" + linkGeneration);
- }
-
- if (!intent.hasCategory(CATEGORY_BROWSABLE)
- || !intent.hasCategory(CATEGORY_DEFAULT)) {
- undefinedList.add(info);
- continue;
- }
- // Use link-enabled generation as preferredOrder, i.e.
- // prefer newly-enabled over earlier-enabled.
- info.preferredOrder = linkGeneration;
+ boolean isAlways = mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, userId);
+ if (isAlways) {
alwaysList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + never: " + info.activityInfo.packageName);
- }
- neverList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + always-ask: " + info.activityInfo.packageName);
- }
- alwaysAskList.add(info);
- } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
- status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
- if (DEBUG_DOMAIN_VERIFICATION || debug) {
- Slog.i(TAG, " + ask: " + info.activityInfo.packageName);
- }
+ } else {
undefinedList.add(info);
}
+ continue;
}
}
@@ -2822,25 +2623,12 @@ public class PackageManagerService extends IPackageManager.Stub
// Add all undefined apps as we want them to appear in the disambiguation dialog.
result.addAll(undefinedList);
// Maybe add one for the other profile.
- if (xpDomainInfo != null && (
- xpDomainInfo.bestDomainVerificationStatus
- != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
+ if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
result.add(xpDomainInfo.resolveInfo);
}
includeBrowser = true;
}
- // The presence of any 'always ask' alternatives means we'll also offer browsers.
- // If there were 'always' entries their preferred order has been set, so we also
- // back that off to make the alternatives equivalent
- if (alwaysAskList.size() > 0) {
- for (ResolveInfo i : result) {
- i.preferredOrder = 0;
- }
- result.addAll(alwaysAskList);
- includeBrowser = true;
- }
-
if (includeBrowser) {
// Also add browsers (all of them or only the default one)
if (DEBUG_DOMAIN_VERIFICATION) {
@@ -2891,7 +2679,6 @@ public class PackageManagerService extends IPackageManager.Stub
// has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
if (result.size() == 0) {
result.addAll(candidates);
- result.removeAll(neverList);
}
}
return result;
@@ -2985,21 +2772,16 @@ public class PackageManagerService extends IPackageManager.Stub
if (ps == null) {
continue;
}
- long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
- int status = (int)(verificationState >> 32);
if (result == null) {
result = new CrossProfileDomainInfo();
result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
sourceUserId, parentUserId);
- result.bestDomainVerificationStatus = status;
- } else {
- result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
- result.bestDomainVerificationStatus);
}
+
+ result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
}
- // Don't consider matches with status NEVER across profiles.
- if (result != null && result.bestDomainVerificationStatus
- == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ if (result != null && !result.wereAnyDomainsVerificationApproved) {
return null;
}
return result;
@@ -3242,26 +3024,20 @@ public class PackageManagerService extends IPackageManager.Stub
final String packageName = info.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps.getInstantApp(userId)) {
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int)(packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
- // there's a local instant application installed, but, the user has
- // chosen to never use it; skip resolution and don't acknowledge
- // an instant application is even available
+ if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName);
+ Slog.v(TAG, "Instant app approvd for intent; pkg: "
+ + packageName);
}
- blockResolution = true;
- break;
+ localInstantApp = info;
} else {
- // we have a locally installed instant application; skip resolution
- // but acknowledge there's an instant application available
if (DEBUG_INSTANT) {
- Slog.v(TAG, "Found installed instant app; pkg: " + packageName);
+ Slog.v(TAG, "Instant app not approved for intent; pkg: "
+ + packageName);
}
- localInstantApp = info;
- break;
+ blockResolution = true;
}
+ break;
}
}
}
@@ -4175,14 +3951,10 @@ public class PackageManagerService extends IPackageManager.Stub
if (ps != null) {
// only check domain verification status if the app is not a browser
if (!info.handleAllWebDataURI) {
- // Try to get the status from User settings first
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int) (packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
- || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "DENY instant app;"
- + " pkg: " + packageName + ", status: " + status);
+ Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
+ + ", approved");
}
return false;
}
@@ -4475,21 +4247,6 @@ public class PackageManagerService extends IPackageManager.Stub
return updateFlagsForComponent(flags, userId);
}
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- public long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
- long result = ps.getDomainVerificationStatusForUser(userId);
- // if none available, get the status
- if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
- if (ps.getIntentFilterVerificationInfo() != null) {
- result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
- }
- }
- return result;
- }
-
/**
* Checks if the request is from the system or an app that has the appropriate cross-user
* permissions defined as follows:
@@ -5339,55 +5096,6 @@ public class PackageManagerService extends IPackageManager.Stub
params.handleIntegrityVerificationFinished();
break;
}
- case START_INTENT_FILTER_VERIFICATIONS: {
- IFVerificationParams params = (IFVerificationParams) msg.obj;
- verifyIntentFiltersIfNeeded(params.userId, params.verifierUid, params.replacing,
- params.packageName, params.hasDomainUrls, params.activities);
- break;
- }
- case INTENT_FILTER_VERIFIED: {
- final int verificationId = msg.arg1;
-
- final IntentFilterVerificationState state = mIntentFilterVerificationStates.get(
- verificationId);
- if (state == null) {
- Slog.w(TAG, "Invalid IntentFilter verification token "
- + verificationId + " received");
- break;
- }
-
- final int userId = state.getUserId();
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Processing IntentFilter verification with token:"
- + verificationId + " and userId:" + userId);
-
- final IntentFilterVerificationResponse response =
- (IntentFilterVerificationResponse) msg.obj;
-
- state.setVerifierResponse(response.callerUid, response.code);
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "IntentFilter verification with token:" + verificationId
- + " and userId:" + userId
- + " is settings verifier response with response code:"
- + response.code);
-
- if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Domains failing verification: "
- + response.getFailedDomainsString());
- }
-
- if (state.isVerificationComplete()) {
- mIntentFilterVerifier.receiveVerificationResponse(verificationId);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "IntentFilter verification with token:" + verificationId
- + " was not said to be complete");
- }
-
- break;
- }
case INSTANT_APP_RESOLUTION_PHASE_TWO: {
InstantAppResolver.doInstantAppResolutionPhaseTwo(mContext,
mInstantAppResolverConnection,
@@ -5448,6 +5156,12 @@ public class PackageManagerService extends IPackageManager.Stub
}
break;
}
+ case DOMAIN_VERIFICATION: {
+ int messageCode = msg.arg1;
+ Object object = msg.obj;
+ mDomainVerificationManager.runMessage(messageCode, object);
+ break;
+ }
}
}
}
@@ -6014,7 +5728,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
public static PackageManagerService main(Context context, Installer installer,
- boolean factoryTest, boolean onlyCore) {
+ @NonNull DomainVerificationService domainVerificationService, boolean factoryTest,
+ boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
@@ -6037,7 +5752,8 @@ public class PackageManagerService extends IPackageManager.Stub
lock),
(i, pm) -> new Settings(Environment.getDataDirectory(),
RuntimePermissionsPersistence.createInstance(),
- i.getPermissionManagerServiceInternal(), lock),
+ i.getPermissionManagerServiceInternal(),
+ domainVerificationService, lock),
(i, pm) -> AppsFilter.create(pm.mPmInternal, i),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
(i, pm) -> SystemConfig.getInstance(),
@@ -6070,6 +5786,13 @@ public class PackageManagerService extends IPackageManager.Stub
i.getContext(), cn, Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE),
(i, pm) -> new ModuleInfoProvider(i.getContext(), pm),
(i, pm) -> LegacyPermissionManagerService.create(i.getContext()),
+ (i, pm) -> domainVerificationService,
+ (i, pm) -> {
+ HandlerThread thread = new ServiceThread(TAG,
+ Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+ thread.start();
+ return pm.new PackageHandler(thread.getLooper());
+ },
new DefaultSystemWrapper(),
LocalServices::getService,
context::getSystemService);
@@ -6241,6 +5964,9 @@ public class PackageManagerService extends IPackageManager.Stub
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mUserManager = injector.getUserManagerService();
+ mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+ mHandler = injector.getHandler();
+
mApexManager = testParams.apexManager;
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
@@ -6250,14 +5976,11 @@ public class PackageManagerService extends IPackageManager.Stub
mDexManager = testParams.dexManager;
mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
mFactoryTest = testParams.factoryTest;
- mHandler = testParams.handler;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
mInstantAppRegistry = testParams.instantAppRegistry;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
- mIntentFilterVerifier = testParams.intentFilterVerifier;
- mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreNUpgrade = testParams.isPreNupgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
@@ -6463,6 +6186,9 @@ public class PackageManagerService extends IPackageManager.Stub
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
mAppLib32InstallDir = getAppLib32InstallDir();
+ mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+ mDomainVerificationManager.setConnection(mDomainVerificationConnection);
+
// Link up the watchers
mPackages.registerObserver(mWatcher);
mSharedLibraries.registerObserver(mWatcher);
@@ -6485,10 +6211,7 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mInstallLock) {
// writer
synchronized (mLock) {
- HandlerThread handlerThread = new ServiceThread(TAG,
- Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
- handlerThread.start();
- mHandler = new PackageHandler(handlerThread.getLooper());
+ mHandler = injector.getHandler();
mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
@@ -6973,7 +6696,6 @@ public class PackageManagerService extends IPackageManager.Stub
if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) {
for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
mSettings.applyDefaultPreferredAppsLPw(user.id);
- primeDomainVerificationsLPw(user.id);
}
}
@@ -7080,13 +6802,18 @@ public class PackageManagerService extends IPackageManager.Stub
mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
mRequiredInstallerPackage = getRequiredInstallerLPr();
mRequiredUninstallerPackage = getRequiredUninstallerLPr();
- mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
- if (mIntentFilterVerifierComponent != null) {
- mIntentFilterVerifier = new IntentVerifierProxy(mContext,
- mIntentFilterVerifierComponent);
- } else {
- mIntentFilterVerifier = null;
- }
+ ComponentName intentFilterVerifierComponent =
+ getIntentFilterVerifierComponentNameLPr();
+ ComponentName domainVerificationAgent =
+ getDomainVerificationAgentComponentNameLPr();
+
+ DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
+ intentFilterVerifierComponent, domainVerificationAgent, mContext,
+ mDomainVerificationManager, mDomainVerificationManager.getCollector(),
+ mDomainVerificationConnection);
+
+ mDomainVerificationManager.setProxy(domainVerificationProxy);
+
mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
@@ -7095,8 +6822,6 @@ public class PackageManagerService extends IPackageManager.Stub
mRequiredVerifierPackage = null;
mRequiredInstallerPackage = null;
mRequiredUninstallerPackage = null;
- mIntentFilterVerifierComponent = null;
- mIntentFilterVerifier = null;
mServicesExtensionPackageName = null;
mSharedSystemSharedLibraryPackageName = null;
}
@@ -7631,6 +7356,40 @@ public class PackageManagerService extends IPackageManager.Stub
return null;
}
+ @Nullable
+ private ComponentName getDomainVerificationAgentComponentNameLPr() {
+ Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
+ List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+ ResolveInfo best = null;
+ final int N = matches.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo cur = matches.get(i);
+ final String packageName = cur.getComponentInfo().packageName;
+ if (checkPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Domain verification agent found but does not hold permission: "
+ + packageName);
+ continue;
+ }
+
+ if (best == null || cur.priority > best.priority) {
+ if (cur.getComponentInfo().enabled) {
+ best = cur;
+ } else {
+ Slog.w(TAG, "Domain verification agent found but not enabled");
+ }
+ }
+ }
+
+ if (best != null) {
+ return best.getComponentInfo().getComponentName();
+ }
+ Slog.w(TAG, "Domain verification agent not found");
+ return null;
+ }
+
@Override
public @Nullable ComponentName getInstantAppResolverComponent() {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
@@ -7764,60 +7523,6 @@ public class PackageManagerService extends IPackageManager.Stub
return matches.get(0).getComponentInfo().getComponentName();
}
- @GuardedBy("mLock")
- private void primeDomainVerificationsLPw(int userId) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Priming domain verifications in user " + userId);
- }
-
- SystemConfig systemConfig = mInjector.getSystemConfig();
- ArraySet<String> packages = systemConfig.getLinkedApps();
-
- for (String packageName : packages) {
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- if (!pkg.isSystem()) {
- Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
- continue;
- }
-
- ArraySet<String> domains = null;
- for (ParsedActivity a : pkg.getActivities()) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- if (hasValidDomains(filter)) {
- if (domains == null) {
- domains = new ArraySet<>();
- }
- domains.addAll(filter.getHostsList());
- }
- }
- }
-
- if (domains != null && domains.size() > 0) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.v(TAG, " + " + packageName);
- }
- // 'Undefined' in the global IntentFilterVerificationInfo, i.e. the usual
- // state w.r.t. the formal app-linkage "no verification attempted" state;
- // and then 'always' in the per-user state actually used for intent resolution.
- final IntentFilterVerificationInfo ivi;
- ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, domains);
- ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
- mSettings.updateIntentFilterVerificationStatusLPw(packageName,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, userId);
- } else {
- Slog.w(TAG, "Sysconfig <app-link> package '" + packageName
- + "' does not handle web links");
- }
- } else {
- Slog.w(TAG, "Unknown package " + packageName + " in sysconfig <app-link>");
- }
- }
-
- scheduleWritePackageRestrictionsLocked(userId);
- scheduleWriteSettingsLocked();
- }
-
private boolean packageIsBrowser(String packageName, int userId) {
List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
PackageManager.MATCH_ALL, userId);
@@ -9663,9 +9368,8 @@ public class PackageManagerService extends IPackageManager.Stub
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
final PackageSetting ps = mSettings.getPackageLPr(packageName);
- final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- final int status = (int)(packedStatus >> 32);
- if (status != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ if (ps != null && mDomainVerificationManager
+ .isApprovedForDomain(ps, intent, userId)) {
return ri;
}
}
@@ -10157,8 +9861,7 @@ public class PackageManagerService extends IPackageManager.Stub
private static class CrossProfileDomainInfo {
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
ResolveInfo resolveInfo;
- /* Best domain verification status of the activities found in the other profile */
- int bestDomainVerificationStatus;
+ boolean wereAnyDomainsVerificationApproved;
}
private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -10244,13 +9947,6 @@ public class PackageManagerService extends IPackageManager.Stub
xpDomainInfo, userId, debug);
}
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
- return liveComputer().getDomainVerificationStatusLPr(ps, userId);
- }
private ResolveInfo querySkipCurrentProfileIntents(
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
@@ -13500,7 +13196,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int userId = user == null ? 0 : user.getIdentifier();
// Modify state for the given package setting
- commitPackageSettings(pkg, oldPkg, pkgSetting, scanFlags,
+ commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
(parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
@@ -13757,6 +13453,9 @@ public class PackageManagerService extends IPackageManager.Stub
usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
}
+
+ final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
+
// TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
// to avoid adding something that's unsupported due to lack of state, since it's called
// with null.
@@ -13780,7 +13479,8 @@ public class PackageManagerService extends IPackageManager.Stub
parsedPackage.getVersionCode(), pkgFlags, pkgPrivateFlags, user,
true /*allowInstall*/, instantApp, virtualPreload,
UserManagerService.getInstance(), usesStaticLibraries,
- parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups());
+ parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
+ newDomainSetId);
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
@@ -13799,7 +13499,7 @@ public class PackageManagerService extends IPackageManager.Stub
PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
UserManagerService.getInstance(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups());
+ parsedPackage.getMimeGroups(), newDomainSetId);
}
if (createNewPackage && originalPkgSetting != null) {
// This is the initial transition from the original package, so,
@@ -14644,8 +14344,8 @@ public class PackageManagerService extends IPackageManager.Stub
* Adds a scanned package to the system. When this method is finished, the package will
* be available for query, resolution, etc...
*/
- private void commitPackageSettings(AndroidPackage pkg,
- @Nullable AndroidPackage oldPkg, PackageSetting pkgSetting,
+ private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+ @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
final @ScanFlags int scanFlags, boolean chatty, ReconciledPackage reconciledPkg) {
final String pkgName = pkg.getPackageName();
if (mCustomResolverComponentName != null &&
@@ -14768,6 +14468,12 @@ public class PackageManagerService extends IPackageManager.Stub
mAppsFilter.addPackage(pkgSetting, isReplace);
mPackageProperty.addAllProperties(pkg);
+ if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
+ mDomainVerificationManager.addPackage(pkgSetting);
+ } else {
+ mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+ }
+
int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
StringBuilder r = null;
int i;
@@ -16440,77 +16146,31 @@ public class PackageManagerService extends IPackageManager.Stub
return DEFAULT_INTEGRITY_VERIFY_ENABLE;
}
+ @Deprecated
@Override
- public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
- throws RemoteException {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
- "Only intentfilter verification agents can verify applications");
-
- final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED);
- final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse(
- Binder.getCallingUid(), verificationCode, failedDomains);
- msg.arg1 = id;
- msg.obj = response;
- mHandler.sendMessage(msg);
+ public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) {
+ DomainVerificationProxyV1.queueLegacyVerifyResult(mContext, mDomainVerificationConnection,
+ id, verificationCode, failedDomains, Binder.getCallingUid());
}
+ @Deprecated
@Override
public int getIntentVerificationStatus(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getUserId(callingUid) != userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "getIntentVerificationStatus" + userId);
- }
- if (getInstantAppPackageName(callingUid) != null) {
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null
- || shouldFilterApplicationLocked(
- ps, callingUid, UserHandle.getUserId(callingUid))) {
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
- }
+ return mDomainVerificationManager.getLegacyState(packageName, userId);
}
+ @Deprecated
@Override
public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-
- boolean result = false;
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplicationLocked(
- ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
- return false;
- }
- result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
- }
- if (result) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- return result;
+ mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
+ return true;
}
+ @Deprecated
@Override
public @NonNull ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications(
String packageName) {
- final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return ParceledListSlice.emptyList();
- }
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplicationLocked(ps, callingUid, UserHandle.getUserId(callingUid))) {
- return ParceledListSlice.emptyList();
- }
- return new ParceledListSlice<>(mSettings.getIntentFilterVerificationsLPr(packageName));
- }
+ return ParceledListSlice.emptyList();
}
@Override
@@ -20193,13 +19853,6 @@ public class PackageManagerService extends IPackageManager.Stub
"Failed to set up verity: " + e);
}
- if (!instantApp) {
- startIntentFilterVerifications(args.user.getIdentifier(), replace, parsedPackage);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
- }
- }
final PackageFreezer freezer =
freezePackageForInstall(pkgName, installFlags, "installPackageLI");
boolean shouldCloseFreezerBeforeReturn = true;
@@ -20533,190 +20186,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private void startIntentFilterVerifications(int userId, boolean replacing, AndroidPackage pkg) {
- if (mIntentFilterVerifierComponent == null) {
- Slog.w(TAG, "No IntentFilter verification will not be done as "
- + "there is no IntentFilterVerifier available!");
- return;
- }
-
- final int verifierUid = getPackageUid(
- mIntentFilterVerifierComponent.getPackageName(),
- MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
-
- Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
- msg.obj = new IFVerificationParams(
- pkg.getPackageName(),
- pkg.isHasDomainUrls(),
- pkg.getActivities(),
- replacing,
- userId,
- verifierUid
- );
- mHandler.sendMessage(msg);
- }
-
- private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing,
- String packageName,
- boolean hasDomainUrls,
- List<ParsedActivity> activities) {
- int size = activities.size();
- if (size == 0) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "No activity, so no need to verify any IntentFilter!");
- return;
- }
-
- if (!hasDomainUrls) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "No domain URLs, so no need to verify any IntentFilter!");
- return;
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Checking for userId:" + userId
- + " if any IntentFilter from the " + size
- + " Activities needs verification ...");
-
- int count = 0;
- boolean handlesWebUris = false;
- ArraySet<String> domains = new ArraySet<>();
- final boolean previouslyVerified;
- boolean hostSetExpanded = false;
- boolean needToRunVerify = false;
- synchronized (mLock) {
- // If this is a new install and we see that we've already run verification for this
- // package, we have nothing to do: it means the state was restored from backup.
- IntentFilterVerificationInfo ivi =
- mSettings.getIntentFilterVerificationLPr(packageName);
- previouslyVerified = (ivi != null);
- if (!replacing && previouslyVerified) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Package " + packageName + " already verified: status="
- + ivi.getStatusString());
- }
- return;
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " Previous verified hosts: "
- + (ivi == null ? "[none]" : ivi.getDomainsString()));
- }
-
- // If any filters need to be verified, then all need to be. In addition, we need to
- // know whether an updating app has any web navigation intent filters, to re-
- // examine handling policy even if not re-verifying.
- final boolean needsVerification = needsNetworkVerificationLPr(packageName);
- for (ParsedActivity a : activities) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- if (filter.handlesWebUris(true)) {
- handlesWebUris = true;
- }
- if (needsVerification && filter.needsVerification()) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "autoVerify requested, processing all filters");
- }
- needToRunVerify = true;
- // It's safe to break out here because filter.needsVerification()
- // can only be true if filter.handlesWebUris(true) returned true, so
- // we've already noted that.
- break;
- }
- }
- }
-
- // Compare the new set of recognized hosts if the app is either requesting
- // autoVerify or has previously used autoVerify but no longer does.
- if (needToRunVerify || previouslyVerified) {
- final int verificationId = mIntentFilterVerificationToken++;
- for (ParsedActivity a : activities) {
- for (ParsedIntentInfo filter : a.getIntents()) {
- // Run verification against hosts mentioned in any web-nav intent filter,
- // even if the filter matches non-web schemes as well
- if (filter.handlesWebUris(false /*onlyWebSchemes*/)) {
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Verification needed for IntentFilter:" + filter.toString());
- mIntentFilterVerifier.addOneIntentFilterVerification(
- verifierUid, userId, verificationId, filter, packageName);
- domains.addAll(filter.getHostsList());
- count++;
- }
- }
- }
- }
-
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " Update published hosts: " + domains.toString());
- }
-
- // If we've previously verified this same host set (or a subset), we can trust that
- // a current ALWAYS policy is still applicable. If this is the case, we're done.
- // (If we aren't in ALWAYS, we want to reverify to allow for apps that had failing
- // hosts in their intent filters, then pushed a new apk that removed them and now
- // passes.)
- //
- // Cases:
- // + still autoVerify (needToRunVerify):
- // - preserve current state if all of: unexpanded, in always
- // - otherwise rerun as usual (fall through)
- // + no longer autoVerify (alreadyVerified && !needToRunVerify)
- // - wipe verification history always
- // - preserve current state if all of: unexpanded, in always
- hostSetExpanded = !previouslyVerified
- || (ivi != null && !ivi.getDomains().containsAll(domains));
- final int currentPolicy =
- mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
- final boolean keepCurState = !hostSetExpanded
- && currentPolicy == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-
- if (needToRunVerify && keepCurState) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Host set not expanding + ALWAYS -> no need to reverify");
- }
- ivi.setDomains(domains);
- scheduleWriteSettingsLocked();
- return;
- } else if (previouslyVerified && !needToRunVerify) {
- // Prior autoVerify state but not requesting it now. Clear autoVerify history,
- // and preserve the always policy iff the host set is not expanding.
- clearIntentFilterVerificationsLPw(packageName, userId, !keepCurState);
- return;
- }
- }
-
- if (needToRunVerify && count > 0) {
- // app requested autoVerify and has at least one matching intent filter
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
- + " IntentFilter verification" + (count > 1 ? "s" : "")
- + " for userId:" + userId);
- mIntentFilterVerifier.startVerifications(userId);
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "No web filters or no new host policy for " + packageName);
- }
- }
- }
-
- @GuardedBy("mLock")
- private boolean needsNetworkVerificationLPr(String packageName) {
- IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
- packageName);
- if (ivi == null) {
- return true;
- }
- int status = ivi.getStatus();
- switch (status) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
- return true;
-
- default:
- // Nothing to do
- return false;
- }
- }
-
private static boolean isExternal(PackageSetting ps) {
return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
@@ -21386,7 +20855,7 @@ public class PackageManagerService extends IPackageManager.Stub
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
- clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true);
+ mDomainVerificationManager.clearPackage(deletedPs.name);
clearDefaultBrowserIfNeeded(packageName);
mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
mAppsFilter.removePackage(getPackageSetting(packageName));
@@ -21913,8 +21382,6 @@ public class PackageManagerService extends IPackageManager.Stub
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- ps.readUserState(nextUserId).domainVerificationStatus,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -22464,40 +21931,6 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
}
- /** This method takes a specific user id as well as UserHandle.USER_ALL. */
- @GuardedBy("mLock")
- private void clearIntentFilterVerificationsLPw(int userId) {
- final int packageCount = mPackages.size();
- for (int i = 0; i < packageCount; i++) {
- AndroidPackage pkg = mPackages.valueAt(i);
- clearIntentFilterVerificationsLPw(pkg.getPackageName(), userId, true);
- }
- }
-
- /** This method takes a specific user id as well as UserHandle.USER_ALL. */
- @GuardedBy("mLock")
- void clearIntentFilterVerificationsLPw(String packageName, int userId,
- boolean alsoResetStatus) {
- if (SystemConfig.getInstance().getLinkedApps().contains(packageName)) {
- // Nope, need to preserve the system configuration approval for this app
- return;
- }
-
- if (userId == UserHandle.USER_ALL) {
- if (mSettings.removeIntentFilterVerificationLPw(packageName,
- mUserManager.getUserIds())) {
- for (int oneUserId : mUserManager.getUserIds()) {
- scheduleWritePackageRestrictionsLocked(oneUserId);
- }
- }
- } else {
- if (mSettings.removeIntentFilterVerificationLPw(packageName, userId,
- alsoResetStatus)) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
- }
-
/** Clears state for all users, and touches intent filter verification policy */
void clearDefaultBrowserIfNeeded(String packageName) {
for (int oneUserId : mUserManager.getUserIds()) {
@@ -22553,8 +21986,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
synchronized (mLock) {
mSettings.applyDefaultPreferredAppsLPw(userId);
- clearIntentFilterVerificationsLPw(userId);
- primeDomainVerificationsLPw(userId);
+ mDomainVerificationManager.clearUser(userId);
final int numPackages = mPackages.size();
for (int i = 0; i < numPackages; i++) {
final AndroidPackage pkg = mPackages.valueAt(i);
@@ -22816,28 +22248,8 @@ public class PackageManagerService extends IPackageManager.Stub
throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()");
}
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- try {
- final TypedXmlSerializer serializer = Xml.newFastSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_INTENT_FILTER_VERIFICATION);
-
- synchronized (mLock) {
- mSettings.writeAllDomainVerificationsLPr(serializer, userId);
- }
-
- serializer.endTag(null, TAG_INTENT_FILTER_VERIFICATION);
- serializer.endDocument();
- serializer.flush();
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Unable to write default apps for backup", e);
- }
- return null;
- }
-
- return dataStream.toByteArray();
+ // TODO(b/170746586)
+ return null;
}
@Override
@@ -22845,22 +22257,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system may call restorePreferredActivities()");
}
-
- try {
- final TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
- restoreFromXml(parser, userId, TAG_INTENT_FILTER_VERIFICATION,
- (parser1, userId1) -> {
- synchronized (mLock) {
- mSettings.readAllDomainVerificationsLPr(parser1, userId1);
- writeSettingsLPrTEMP();
- }
- });
- } catch (Exception e) {
- if (DEBUG_BACKUP) {
- Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
- }
- }
+ // TODO(b/170746586)
}
@Override
@@ -24113,8 +23510,8 @@ public class PackageManagerService extends IPackageManager.Stub
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new PackageManagerShellCommand(this, mContext)).exec(
- this, in, out, err, args, callback, resultReceiver);
+ (new PackageManagerShellCommand(this, mContext,mDomainVerificationManager.getShell()))
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
@SuppressWarnings("resource")
@@ -24296,9 +23693,8 @@ public class PackageManagerService extends IPackageManager.Stub
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
- } else if ("i".equals(cmd) || "ifv".equals(cmd)
- || "intent-filter-verifiers".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS);
+ } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
} else if ("version".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
@@ -24392,14 +23788,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) &&
+ if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER) &&
packageName == null) {
- if (mIntentFilterVerifierComponent != null) {
- String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+ ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
if (!checkin) {
if (dumpState.onTitlePrinted())
pw.println();
- pw.println("Intent Filter Verifier:");
+ pw.println("Domain Verifier:");
pw.print(" Using: ");
pw.print(verifierPackageName);
pw.print(" (uid=");
@@ -24407,14 +23805,14 @@ public class PackageManagerService extends IPackageManager.Stub
UserHandle.USER_SYSTEM));
pw.println(")");
} else if (verifierPackageName != null) {
- pw.print("ifv,"); pw.print(verifierPackageName);
+ pw.print("dv,"); pw.print(verifierPackageName);
pw.print(",");
pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.USER_SYSTEM));
}
} else {
pw.println();
- pw.println("No Intent Filter Verifier available!");
+ pw.println("No Domain Verifier available!");
}
}
@@ -24531,63 +23929,20 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)
- && packageName == null) {
- pw.println();
- int count = mSettings.getPackagesLocked().size();
- if (count == 0) {
- pw.println("No applications!");
- pw.println();
- } else {
- final String prefix = " ";
- Collection<PackageSetting> allPackageSettings =
- mSettings.getPackagesLocked().values();
- if (allPackageSettings.size() == 0) {
- pw.println("No domain preferred apps!");
- pw.println();
- } else {
- pw.println("App verification status:");
- pw.println();
- count = 0;
- for (PackageSetting ps : allPackageSettings) {
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null || ivi.getPackageName() == null) continue;
- pw.println(prefix + "Package: " + ivi.getPackageName());
- pw.println(prefix + "Domains: " + ivi.getDomainsString());
- pw.println(prefix + "Status: " + ivi.getStatusString());
- pw.println();
- count++;
- }
- if (count == 0) {
- pw.println(prefix + "No app verification established.");
- pw.println();
- }
- for (int userId : mUserManager.getUserIds()) {
- pw.println("App linkages for user " + userId + ":");
- pw.println();
- count = 0;
- for (PackageSetting ps : allPackageSettings) {
- final long status = ps.getDomainVerificationStatusForUser(userId);
- if (status >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
- && !DEBUG_DOMAIN_VERIFICATION) {
- continue;
- }
- pw.println(prefix + "Package: " + ps.name);
- pw.println(prefix + "Domains: " + dumpDomainString(ps.name));
- String statusStr = IntentFilterVerificationInfo.
- getStatusStringFromValue(status);
- pw.println(prefix + "Status: " + statusStr);
- pw.println();
- count++;
- }
- if (count == 0) {
- pw.println(prefix + "No configured app linkages.");
- pw.println();
- }
- }
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ android.util.IndentingPrintWriter writer =
+ new android.util.IndentingPrintWriter(pw);
+ if (dumpState.onTitlePrinted()) pw.println();
+
+ writer.println("Domain verification status:");
+ writer.increaseIndent();
+ try {
+ mDomainVerificationManager.printState(writer, packageName, UserHandle.USER_ALL);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Failure printing domain verification information");
+ Slog.e(TAG, "Failure printing domain verification information", e);
}
+ writer.decreaseIndent();
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
@@ -24776,8 +24131,10 @@ public class PackageManagerService extends IPackageManager.Stub
UserHandle.USER_SYSTEM));
proto.end(requiredVerifierPackageToken);
- if (mIntentFilterVerifierComponent != null) {
- String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+ ComponentName verifierComponent = proxy.getComponentName();
+ if (verifierComponent != null) {
+ String verifierPackageName = verifierComponent.getPackageName();
final long verifierPackageToken =
proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
@@ -24902,35 +24259,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private String dumpDomainString(String packageName) {
- List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
- .getList();
- List<IntentFilter> filters = getAllIntentFilters(packageName).getList();
-
- ArraySet<String> result = new ArraySet<>();
- if (iviList.size() > 0) {
- for (IntentFilterVerificationInfo ivi : iviList) {
- result.addAll(ivi.getDomains());
- }
- }
- if (filters != null && filters.size() > 0) {
- for (IntentFilter filter : filters) {
- if (filter.hasCategory(Intent.CATEGORY_BROWSABLE)
- && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
- filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
- result.addAll(filter.getHostsList());
- }
- }
- }
-
- StringBuilder sb = new StringBuilder(result.size() * 16);
- for (String domain : result) {
- if (sb.length() > 0) sb.append(" ");
- sb.append(domain);
- }
- return sb.toString();
- }
-
// ------- apps on sdcard specific code -------
static final boolean DEBUG_SD_INSTALL = false;
@@ -26126,7 +25454,6 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
scheduleWritePackageListLocked(userId);
- primeDomainVerificationsLPw(userId);
mAppsFilter.onUsersChanged();
}
}
@@ -28426,6 +27753,11 @@ public class PackageManagerService extends IPackageManager.Stub
duration);
return bOptions;
}
+
+ @NonNull
+ public DomainVerificationService.Connection getDomainVerificationConnection() {
+ return mDomainVerificationConnection;
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 212edf6d0daa..b5765b50e746 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -17,13 +17,9 @@
package com.android.server.pm;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import android.accounts.IAccountManager;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -108,6 +104,7 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import dalvik.system.DexFile;
@@ -149,6 +146,7 @@ class PackageManagerShellCommand extends ShellCommand {
final LegacyPermissionManagerInternal mLegacyPermissionManager;
final PermissionManager mPermissionManager;
final Context mContext;
+ final DomainVerificationShell mDomainVerificationShell;
final private WeakHashMap<String, Resources> mResourceCache =
new WeakHashMap<String, Resources>();
int mTargetUser;
@@ -158,11 +156,13 @@ class PackageManagerShellCommand extends ShellCommand {
private static final SecureRandom RANDOM = new SecureRandom();
- PackageManagerShellCommand(PackageManagerService service, Context context) {
+ PackageManagerShellCommand(@NonNull PackageManagerService service,
+ @NonNull Context context, @NonNull DomainVerificationShell domainVerificationShell) {
mInterface = service;
mLegacyPermissionManager = LocalServices.getService(LegacyPermissionManagerInternal.class);
mPermissionManager = context.getSystemService(PermissionManager.class);
mContext = context;
+ mDomainVerificationShell = domainVerificationShell;
}
@Override
@@ -267,10 +267,6 @@ class PackageManagerShellCommand extends ShellCommand {
return runGetPrivappDenyPermissions();
case "get-oem-permissions":
return runGetOemPermissions();
- case "set-app-link":
- return runSetAppLink();
- case "get-app-link":
- return runGetAppLink();
case "trim-caches":
return runTrimCaches();
case "create-user":
@@ -309,6 +305,12 @@ class PackageManagerShellCommand extends ShellCommand {
case "bypass-staged-installer-check":
return runBypassStagedInstallerCheck();
default: {
+ Boolean domainVerificationResult =
+ mDomainVerificationShell.runCommand(this, cmd);
+ if (domainVerificationResult != null) {
+ return domainVerificationResult ? 0 : 1;
+ }
+
String nextArg = getNextArg();
if (nextArg == null) {
if (cmd.equalsIgnoreCase("-l")) {
@@ -2427,134 +2429,6 @@ class PackageManagerShellCommand extends ShellCommand {
return 0;
}
- private String linkStateToString(int state) {
- switch (state) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
- }
- return "Unknown link state: " + state;
- }
-
- // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
- private int runSetAppLink() throws RemoteException {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if (opt.equals("--user")) {
- userId = UserHandle.parseUserArg(getNextArgRequired());
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return 1;
- }
- }
-
- // Package name to act on; required
- final String pkg = getNextArg();
- if (pkg == null) {
- getErrPrintWriter().println("Error: no package specified.");
- return 1;
- }
-
- // State to apply; {always|ask|never|undefined}, required
- final String modeString = getNextArg();
- if (modeString == null) {
- getErrPrintWriter().println("Error: no app link state specified.");
- return 1;
- }
-
- final int newMode;
- switch (modeString.toLowerCase()) {
- case "undefined":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- break;
-
- case "always":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- break;
-
- case "ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
- break;
-
- case "always-ask":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
- break;
-
- case "never":
- newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
- break;
-
- default:
- getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
- return 1;
- }
-
- final int translatedUserId =
- translateUserId(userId, UserHandle.USER_NULL, "runSetAppLink");
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
- if (info == null) {
- getErrPrintWriter().println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- if (!mInterface.updateIntentVerificationStatus(pkg, newMode, translatedUserId)) {
- getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
- return 1;
- }
-
- return 0;
- }
-
- // pm get-app-link [--user USER_ID] PACKAGE
- private int runGetAppLink() throws RemoteException {
- int userId = UserHandle.USER_SYSTEM;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if (opt.equals("--user")) {
- userId = UserHandle.parseUserArg(getNextArgRequired());
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return 1;
- }
- }
-
- // Package name to act on; required
- final String pkg = getNextArg();
- if (pkg == null) {
- getErrPrintWriter().println("Error: no package specified.");
- return 1;
- }
-
- final int translatedUserId =
- translateUserId(userId, UserHandle.USER_NULL, "runGetAppLink");
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
- if (info == null) {
- getErrPrintWriter().println("Error: package " + pkg + " not found.");
- return 1;
- }
-
- if ((info.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
- getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
- return 1;
- }
-
- getOutPrintWriter().println(linkStateToString(
- mInterface.getIntentVerificationStatus(pkg, translatedUserId)));
-
- return 0;
- }
-
private int runTrimCaches() throws RemoteException {
String size = getNextArg();
if (size == null) {
@@ -3915,6 +3789,8 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" --enable: turn on debug logging (default)");
pw.println(" --disable: turn off debug logging");
pw.println("");
+ mDomainVerificationShell.printHelp(pw);
+ pw.println("");
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index ade087be30c9..69e84b536004 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -39,6 +39,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
/**
* Settings data for a particular package we know about.
@@ -99,18 +100,23 @@ public class PackageSetting extends PackageSettingBase {
@NonNull
private PackageStateUnserialized pkgState = new PackageStateUnserialized();
+ @NonNull
+ private UUID mDomainSetId;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public PackageSetting(String name, String realName, @NonNull File codePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
long pVersionCode, int pkgFlags, int privateFlags,
int sharedUserId, String[] usesStaticLibraries,
- long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups) {
+ long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups,
+ @NonNull UUID domainSetId) {
super(name, realName, codePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
pVersionCode, pkgFlags, privateFlags,
usesStaticLibraries, usesStaticLibrariesVersions);
this.sharedUserId = sharedUserId;
+ mDomainSetId = domainSetId;
copyMimeGroups(mimeGroups);
}
@@ -168,6 +174,7 @@ public class PackageSetting extends PackageSettingBase {
sharedUser = orig.sharedUser;
sharedUserId = orig.sharedUserId;
copyMimeGroups(orig.mimeGroups);
+ mDomainSetId = orig.getDomainSetId();
}
private void copyMimeGroups(@Nullable Map<String, ArraySet<String>> newMimeGroups) {
@@ -374,6 +381,7 @@ public class PackageSetting extends PackageSettingBase {
pkg = other.pkg;
sharedUserId = other.sharedUserId;
sharedUser = other.sharedUser;
+ mDomainSetId = other.mDomainSetId;
Set<String> mimeGroupNames = other.mimeGroups != null ? other.mimeGroups.keySet() : null;
updateMimeGroups(mimeGroupNames);
@@ -385,4 +393,14 @@ public class PackageSetting extends PackageSettingBase {
public PackageStateUnserialized getPkgState() {
return pkgState;
}
+
+ @NonNull
+ public UUID getDomainSetId() {
+ return mDomainSetId;
+ }
+
+ public PackageSetting setDomainSetId(@NonNull UUID domainSetId) {
+ mDomainSetId = domainSetId;
+ return this;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 67bd82b42983..8aa553d68b98 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -42,6 +42,8 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.File;
@@ -131,8 +133,6 @@ public abstract class PackageSettingBase extends SettingBase {
/** Whether or not an update is available. Ostensibly only for instant apps. */
boolean updateAvailable;
- IntentFilterVerificationInfo verificationInfo;
-
boolean forceQueryableOverride;
@NonNull
@@ -258,7 +258,6 @@ public abstract class PackageSettingBase extends SettingBase {
for (int i = 0; i < orig.mUserState.size(); i++) {
mUserState.put(orig.mUserState.keyAt(i), orig.mUserState.valueAt(i));
}
- verificationInfo = orig.verificationInfo;
versionCode = orig.versionCode;
volumeUuid = orig.volumeUuid;
categoryHint = orig.categoryHint;
@@ -350,9 +349,12 @@ public abstract class PackageSettingBase extends SettingBase {
return readUserState(userId).getSharedLibraryOverlayPaths();
}
- /** Only use for testing. Do NOT use in production code. */
+ /**
+ * Only use for testing. Do NOT use in production code.
+ */
@VisibleForTesting
- SparseArray<PackageUserState> getUserState() {
+ @Deprecated
+ public SparseArray<PackageUserState> getUserState() {
return mUserState;
}
@@ -496,8 +498,7 @@ public abstract class PackageSettingBase extends SettingBase {
ArrayMap<String, PackageUserState.SuspendParams> suspendParams, boolean instantApp,
boolean virtualPreload, String lastDisableAppCaller,
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
- int domainVerifState, int linkGeneration, int installReason, int uninstallReason,
- String harmfulAppWarning) {
+ int installReason, int uninstallReason, String harmfulAppWarning) {
PackageUserState state = modifyUserState(userId);
state.ceDataInode = ceDataInode;
state.enabled = enabled;
@@ -511,8 +512,6 @@ public abstract class PackageSettingBase extends SettingBase {
state.lastDisableAppCaller = lastDisableAppCaller;
state.enabledComponents = enabledComponents;
state.disabledComponents = disabledComponents;
- state.domainVerificationStatus = domainVerifState;
- state.appLinkGeneration = linkGeneration;
state.installReason = installReason;
state.uninstallReason = uninstallReason;
state.instantApp = instantApp;
@@ -528,7 +527,6 @@ public abstract class PackageSettingBase extends SettingBase {
otherState.instantApp,
otherState.virtualPreload, otherState.lastDisableAppCaller,
otherState.enabledComponents, otherState.disabledComponents,
- otherState.domainVerificationStatus, otherState.appLinkGeneration,
otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning);
}
@@ -644,40 +642,6 @@ public abstract class PackageSettingBase extends SettingBase {
return excludedUserIds;
}
- IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
- return verificationInfo;
- }
-
- void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
- verificationInfo = info;
- onChanged();
- }
-
- // Returns a packed value as a long:
- //
- // high 'int'-sized word: link status: undefined/ask/never/always.
- // low 'int'-sized word: relative priority among 'always' results.
- long getDomainVerificationStatusForUser(int userId) {
- PackageUserState state = readUserState(userId);
- long result = (long) state.appLinkGeneration;
- result |= ((long) state.domainVerificationStatus) << 32;
- return result;
- }
-
- void setDomainVerificationStatusForUser(final int status, int generation, int userId) {
- PackageUserState state = modifyUserState(userId);
- state.domainVerificationStatus = status;
- if (status == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- state.appLinkGeneration = generation;
- onChanged();
- }
- }
-
- void clearDomainVerificationStatusForUser(int userId) {
- modifyUserState(userId).domainVerificationStatus =
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
-
protected void writeUsersInfoToProto(ProtoOutputStream proto, long fieldId) {
int count = mUserState.size();
for (int i = 0; i < count; i++) {
@@ -845,7 +809,6 @@ public abstract class PackageSettingBase extends SettingBase {
this.volumeUuid = other.volumeUuid;
this.categoryHint = other.categoryHint;
this.updateAvailable = other.updateAvailable;
- this.verificationInfo = other.verificationInfo;
this.forceQueryableOverride = other.forceQueryableOverride;
this.incrementalStates = other.incrementalStates;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 349d556eac87..fb033e6594b8 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -21,15 +21,12 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.UNINSTALL_REASON_USER_TYPE;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
-import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull;
@@ -108,6 +105,9 @@ import com.android.permission.persistence.RuntimePermissionsState;
import com.android.server.LocalServices;
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationPersistence;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -131,6 +131,7 @@ import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedWriter;
import java.io.File;
@@ -147,7 +148,6 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -155,6 +155,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
/**
* Holds information about dynamic settings.
@@ -283,9 +284,9 @@ public final class Settings implements Watchable, Snappable {
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
"crossProfile-intent-filters";
- private static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
+ public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
private static final String TAG_DEFAULT_APPS = "default-apps";
- private static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
+ public static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
"all-intent-filter-verifications";
private static final String TAG_DEFAULT_BROWSER = "default-browser";
private static final String TAG_DEFAULT_DIALER = "default-dialer";
@@ -390,12 +391,6 @@ public final class Settings implements Watchable, Snappable {
private final WatchedSparseArray<ArraySet<String>> mBlockUninstallPackages =
new WatchedSparseArray<>();
- // Set of restored intent-filter verification states
- @Watched
- private final WatchedArrayMap<String, IntentFilterVerificationInfo>
- mRestoredIntentFilterVerifications =
- new WatchedArrayMap<String, IntentFilterVerificationInfo>();
-
private static final class KernelPackageState {
int appId;
int[] excludedUserIds;
@@ -487,7 +482,10 @@ public final class Settings implements Watchable, Snappable {
@Watched
final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+ // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
+ // verify.
// App-link priority tracking, per-user
+ @NonNull
@Watched
final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
@@ -512,6 +510,8 @@ public final class Settings implements Watchable, Snappable {
private final LegacyPermissionDataProvider mPermissionDataProvider;
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
+
/**
* The observer that watches for changes from array members
*/
@@ -538,13 +538,12 @@ public final class Settings implements Watchable, Snappable {
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
-
+ mDomainVerificationManager = null;
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
- mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -554,13 +553,14 @@ public final class Settings implements Watchable, Snappable {
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
- mNextAppLinkGeneration.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
}
Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
- LegacyPermissionDataProvider permissionDataProvider, Object lock) {
+ LegacyPermissionDataProvider permissionDataProvider,
+ @NonNull DomainVerificationManagerInternal domainVerificationManager,
+ @NonNull Object lock) {
mLock = lock;
mAppIds = new WatchedArrayList<>();
mOtherAppIds = new WatchedSparseArray<>();
@@ -587,12 +587,13 @@ public final class Settings implements Watchable, Snappable {
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
+ mDomainVerificationManager = domainVerificationManager;
+
mPackages.registerObserver(mObserver);
mInstallerPackages.registerObserver(mObserver);
mKernelMapping.registerObserver(mObserver);
mDisabledSysPackages.registerObserver(mObserver);
mBlockUninstallPackages.registerObserver(mObserver);
- mRestoredIntentFilterVerifications.registerObserver(mObserver);
mVersion.registerObserver(mObserver);
mPreferredActivities.registerObserver(mObserver);
mPersistentPreferredActivities.registerObserver(mObserver);
@@ -602,7 +603,6 @@ public final class Settings implements Watchable, Snappable {
mOtherAppIds.registerObserver(mObserver);
mRenamedPackages.registerObserver(mObserver);
mDefaultBrowserApp.registerObserver(mObserver);
- mNextAppLinkGeneration.registerObserver(mObserver);
Watchable.verifyWatchedAttributes(this, mObserver);
}
@@ -629,11 +629,12 @@ public final class Settings implements Watchable, Snappable {
mBackupStoppedPackagesFilename = null;
mKernelMappingFilename = null;
+ mDomainVerificationManager = r.mDomainVerificationManager;
+
mInstallerPackages.addAll(r.mInstallerPackages);
mKernelMapping.putAll(r.mKernelMapping);
mDisabledSysPackages.putAll(r.mDisabledSysPackages);
mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
- mRestoredIntentFilterVerifications.putAll(r.mRestoredIntentFilterVerifications);
mVersion.putAll(r.mVersion);
mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
WatchedSparseArray.snapshot(
@@ -649,7 +650,6 @@ public final class Settings implements Watchable, Snappable {
mKeySetRefs.putAll(r.mKeySetRefs);
mRenamedPackages.snapshot(r.mRenamedPackages);
mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
- mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
// mReadMessages
mPendingPackages.addAll(r.mPendingPackages);
mSystemDir = null;
@@ -766,7 +766,8 @@ public final class Settings implements Watchable, Snappable {
p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
p.secondaryCpuAbiString, p.cpuAbiOverrideString,
p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,
- p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups);
+ p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups,
+ mDomainVerificationManager.generateNewId());
if (ret != null) {
ret.getPkgState().setUpdatedSystemApp(false);
}
@@ -786,7 +787,8 @@ public final class Settings implements Watchable, Snappable {
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
pkgFlags, int pkgPrivateFlags, String[] usesStaticLibraries,
- long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups) {
+ long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups,
+ @NonNull UUID domainSetId) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.appId == uid) {
@@ -799,7 +801,7 @@ public final class Settings implements Watchable, Snappable {
p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
pkgPrivateFlags, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
- mimeGroups);
+ mimeGroups, domainSetId);
p.appId = uid;
if (registerExistingAppIdLPw(uid, p, name)) {
mPackages.put(name, p);
@@ -863,7 +865,7 @@ public final class Settings implements Watchable, Snappable {
UserHandle installUser, boolean allowInstall, boolean instantApp,
boolean virtualPreload, UserManagerService userManager,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames) {
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -883,12 +885,13 @@ public final class Settings implements Watchable, Snappable {
pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
// Update new package state.
pkgSetting.setTimeStamp(codePath.lastModified());
+ pkgSetting.setDomainSetId(domainSetId);
} else {
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
0 /*sharedUserId*/, usesStaticLibraries,
- usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames));
+ usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames), domainSetId);
pkgSetting.setTimeStamp(codePath.lastModified());
pkgSetting.sharedUser = sharedUser;
// If this is not a system app, it starts out stopped.
@@ -925,8 +928,6 @@ public final class Settings implements Watchable, Snappable {
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -983,7 +984,7 @@ public final class Settings implements Watchable, Snappable {
@Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames)
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
throws PackageManagerException {
final String pkgName = pkgSetting.name;
if (pkgSetting.sharedUser != sharedUser) {
@@ -1059,6 +1060,7 @@ public final class Settings implements Watchable, Snappable {
pkgSetting.usesStaticLibrariesVersions = null;
}
pkgSetting.updateMimeGroups(mimeGroupNames);
+ pkgSetting.setDomainSetId(domainSetId);
}
/**
@@ -1168,15 +1170,6 @@ public final class Settings implements Watchable, Snappable {
replaceAppIdLPw(p.appId, sharedUser);
}
}
-
- IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(p.name);
- if (ivi != null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Applying restored IVI for " + p.name + " : " + ivi.getStatusString());
- }
- mRestoredIntentFilterVerifications.remove(p.name);
- p.setIntentFilterVerificationInfo(ivi);
- }
}
int removePackageLPw(String name) {
@@ -1307,129 +1300,6 @@ public final class Settings implements Watchable, Snappable {
return cpir;
}
- /**
- * The following functions suppose that you have a lock for managing access to the
- * mIntentFiltersVerifications map.
- */
-
- /* package protected */
- IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return null;
- }
- return ps.getIntentFilterVerificationInfo();
- }
-
- /* package protected */
- IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
- ArraySet<String> domains) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return null;
- }
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null) {
- ivi = new IntentFilterVerificationInfo(packageName, domains);
- ps.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(PackageManagerService.TAG,
- "Creating new IntentFilterVerificationInfo for pkg: " + packageName);
- }
- } else {
- ivi.setDomains(domains);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(PackageManagerService.TAG,
- "Setting domains to existing IntentFilterVerificationInfo for pkg: " +
- packageName + " and with domains: " + ivi.getDomainsString());
- }
- }
- return ivi;
- }
-
- int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
- }
- return (int)(ps.getDomainVerificationStatusForUser(userId) >> 32);
- }
-
- boolean updateIntentFilterVerificationStatusLPw(String packageName, final int status, int userId) {
- // Update the status for the current package
- PackageSetting current = mPackages.get(packageName);
- if (current == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return false;
- }
-
- final int alwaysGeneration;
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
- alwaysGeneration = mNextAppLinkGeneration.get(userId) + 1;
- mNextAppLinkGeneration.put(userId, alwaysGeneration);
- } else {
- alwaysGeneration = 0;
- }
-
- current.setDomainVerificationStatusForUser(status, alwaysGeneration, userId);
- return true;
- }
-
- /**
- * Used for Settings App and PackageManagerService dump. Should be read only.
- */
- List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
- String packageName) {
- if (packageName == null) {
- return Collections.<IntentFilterVerificationInfo>emptyList();
- }
- ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
- for (PackageSetting ps : mPackages.values()) {
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
- !ivi.getPackageName().equalsIgnoreCase(packageName)) {
- continue;
- }
- result.add(ivi);
- }
- return result;
- }
-
- boolean removeIntentFilterVerificationLPw(String packageName, int userId,
- boolean alsoResetStatus) {
- PackageSetting ps = mPackages.get(packageName);
- if (ps == null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
- }
- return false;
- }
- if (alsoResetStatus) {
- ps.clearDomainVerificationStatusForUser(userId);
- }
- ps.setIntentFilterVerificationInfo(null);
- return true;
- }
-
- boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
- boolean result = false;
- for (int userId : userIds) {
- result |= removeIntentFilterVerificationLPw(packageName, userId, true);
- }
- return result;
- }
-
String removeDefaultBrowserPackageNameLPw(int userId) {
return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
}
@@ -1591,40 +1461,7 @@ public final class Settings implements Watchable, Snappable {
}
}
- private void readDomainVerificationLPw(TypedXmlPullParser parser,
- PackageSettingBase packageSetting) throws XmlPullParserException, IOException {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- packageSetting.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_PARSER) {
- Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
- }
- }
-
- private void readRestoredIntentFilterVerifications(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- final String tagName = parser.getName();
- if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Restored IVI for " + ivi.getPackageName()
- + " status=" + ivi.getStatusString());
- }
- mRestoredIntentFilterVerifications.put(ivi.getPackageName(), ivi);
- } else {
- Slog.w(TAG, "Unknown element: " + tagName);
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- void readDefaultAppsLPw(TypedXmlPullParser parser, int userId)
+ void readDefaultAppsLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -1727,8 +1564,6 @@ public final class Settings implements Watchable, Snappable {
null /*lastDisableAppCaller*/,
null /*enabledComponents*/,
null /*disabledComponents*/,
- INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0 /*linkGeneration*/,
PackageManager.INSTALL_REASON_UNKNOWN,
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/);
@@ -1753,8 +1588,6 @@ public final class Settings implements Watchable, Snappable {
return;
}
- int maxAppLinkGeneration = 0;
-
int outerDepth = parser.getDepth();
PackageSetting ps = null;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1817,11 +1650,6 @@ public final class Settings implements Watchable, Snappable {
final int verifState = parser.getAttributeInt(null,
ATTR_DOMAIN_VERIFICATON_STATE,
PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
- final int linkGeneration =
- parser.getAttributeInt(null, ATTR_APP_LINK_GENERATION, 0);
- if (linkGeneration > maxAppLinkGeneration) {
- maxAppLinkGeneration = linkGeneration;
- }
final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON,
PackageManager.INSTALL_REASON_UNKNOWN);
final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
@@ -1897,9 +1725,10 @@ public final class Settings implements Watchable, Snappable {
}
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
hidden, distractionFlags, suspended, suspendParamsMap,
- instantApp, virtualPreload,
- enabledCaller, enabledComponents, disabledComponents, verifState,
- linkGeneration, installReason, uninstallReason, harmfulAppWarning);
+ instantApp, virtualPreload, enabledCaller, enabledComponents,
+ disabledComponents, installReason, uninstallReason, harmfulAppWarning);
+
+ mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -1918,9 +1747,6 @@ public final class Settings implements Watchable, Snappable {
}
str.close();
-
- mNextAppLinkGeneration.put(userId, maxAppLinkGeneration + 1);
-
} catch (XmlPullParserException e) {
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR,
@@ -2038,77 +1864,7 @@ public final class Settings implements Watchable, Snappable {
serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
}
- void writeDomainVerificationsLPr(TypedXmlSerializer serializer,
- IntentFilterVerificationInfo verificationInfo)
- throws IllegalArgumentException, IllegalStateException, IOException {
- if (verificationInfo != null && verificationInfo.getPackageName() != null) {
- serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
- verificationInfo.writeToXml(serializer);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Wrote domain verification for package: "
- + verificationInfo.getPackageName());
- }
- serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
- }
- }
-
- // Specifically for backup/restore
- void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId)
- throws IllegalArgumentException, IllegalStateException, IOException {
- serializer.startTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
- final int N = mPackages.size();
- for (int i = 0; i < N; i++) {
- PackageSetting ps = mPackages.valueAt(i);
- IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
- if (ivi != null) {
- writeDomainVerificationsLPr(serializer, ivi);
- }
- }
- serializer.endTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
- }
-
- // Specifically for backup/restore
- void readAllDomainVerificationsLPr(TypedXmlPullParser parser, int userId)
- throws XmlPullParserException, IOException {
- mRestoredIntentFilterVerifications.clear();
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
- final String pkgName = ivi.getPackageName();
- final PackageSetting ps = mPackages.get(pkgName);
- if (ps != null) {
- // known/existing package; update in place
- ps.setIntentFilterVerificationInfo(ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Restored IVI for existing app " + pkgName
- + " status=" + ivi.getStatusString());
- }
- } else {
- mRestoredIntentFilterVerifications.put(pkgName, ivi);
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Restored IVI for pending app " + pkgName
- + " status=" + ivi.getStatusString());
- }
- }
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element under <all-intent-filter-verification>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- void writeDefaultAppsLPr(TypedXmlSerializer serializer, int userId)
+ void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_DEFAULT_APPS);
String defaultBrowser = mDefaultBrowserApp.get(userId);
@@ -2217,15 +1973,6 @@ public final class Settings implements Watchable, Snappable {
ustate.lastDisableAppCaller);
}
}
- if (ustate.domainVerificationStatus !=
- PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
- serializer.attributeInt(null, ATTR_DOMAIN_VERIFICATON_STATE,
- ustate.domainVerificationStatus);
- }
- if (ustate.appLinkGeneration != 0) {
- serializer.attributeInt(null, ATTR_APP_LINK_GENERATION,
- ustate.appLinkGeneration);
- }
if (ustate.installReason != PackageManager.INSTALL_REASON_UNKNOWN) {
serializer.attributeInt(null, ATTR_INSTALL_REASON, ustate.installReason);
}
@@ -2569,22 +2316,7 @@ public final class Settings implements Watchable, Snappable {
}
}
- final int numIVIs = mRestoredIntentFilterVerifications.size();
- if (numIVIs > 0) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Writing restored-ivi entries to packages.xml");
- }
- serializer.startTag(null, "restored-ivi");
- for (int i = 0; i < numIVIs; i++) {
- IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.valueAt(i);
- writeDomainVerificationsLPr(serializer, ivi);
- }
- serializer.endTag(null, "restored-ivi");
- } else {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, " no restored IVI entries to write");
- }
- }
+ mDomainVerificationManager.writeSettings(serializer);
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
@@ -2961,6 +2693,8 @@ public final class Settings implements Watchable, Snappable {
serializer.attributeFloat(null, "loadingProgress",
pkg.getIncrementalStates().getProgress());
+ serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
+
writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
@@ -2973,7 +2707,7 @@ public final class Settings implements Watchable, Snappable {
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
- writeDomainVerificationsLPr(serializer, pkg.verificationInfo);
+ mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
writeMimeGroupLPr(serializer, pkg.mimeGroups);
serializer.endTag(null, "package");
@@ -3104,8 +2838,6 @@ public final class Settings implements Watchable, Snappable {
if (nname != null && oname != null) {
mRenamedPackages.put(nname, oname);
}
- } else if (tagName.equals("restored-ivi")) {
- readRestoredIntentFilterVerifications(parser);
} else if (tagName.equals("last-platform-version")) {
// Upgrade from older XML schema
final VersionInfo internal = findOrCreateVersion(
@@ -3147,6 +2879,11 @@ public final class Settings implements Watchable, Snappable {
ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
+ } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
+ mDomainVerificationManager.readSettings(parser);
+ } else if (tagName.equals(
+ DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+ mDomainVerificationManager.readLegacySettings(parser);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+ parser.getName());
@@ -3628,9 +3365,15 @@ public final class Settings implements Watchable, Snappable {
if (codePathStr.contains("/priv-app/")) {
pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
+
+ // When reading a disabled setting, use a disabled domainSetId, which makes it easier to
+ // debug invalid entries. The actual logic for migrating to a new ID is done in other
+ // methods that use DomainVerificationManagerInternal#generateNewId
+ UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
- versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null);
+ versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null,
+ domainSetId);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
if (timeStamp == 0) {
timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3703,6 +3446,7 @@ public final class Settings implements Watchable, Snappable {
boolean isStartable = false;
boolean isLoading = false;
float loadingProgress = 0;
+ UUID domainSetId;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3739,6 +3483,15 @@ public final class Settings implements Watchable, Snappable {
categoryHint = parser.getAttributeInt(null, "categoryHint",
ApplicationInfo.CATEGORY_UNDEFINED);
+ String domainSetIdString = parser.getAttributeValue(null, "domainSetId");
+
+ if (TextUtils.isEmpty(domainSetIdString)) {
+ // If empty, assume restoring from previous platform version and generate an ID
+ domainSetId = mDomainVerificationManager.generateNewId();
+ } else {
+ domainSetId = UUID.fromString(domainSetIdString);
+ }
+
systemStr = parser.getAttributeValue(null, "publicFlags");
if (systemStr != null) {
try {
@@ -3810,7 +3563,7 @@ public final class Settings implements Watchable, Snappable {
legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags,
null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+ userId + " pkg=" + packageSetting);
@@ -3831,7 +3584,7 @@ public final class Settings implements Watchable, Snappable {
versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
null /*usesStaticLibraries*/,
null /*usesStaticLibraryVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/, domainSetId);
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
@@ -3946,7 +3699,11 @@ public final class Settings implements Watchable, Snappable {
packageSetting.installSource =
packageSetting.installSource.setInitiatingPackageSignatures(signatures);
} else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
- readDomainVerificationLPw(parser, packageSetting);
+ IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
+ mDomainVerificationManager.addLegacySetting(packageSetting.name, ivi);
+ if (DEBUG_PARSER) {
+ Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
+ }
} else if (tagName.equals(TAG_MIME_GROUP)) {
packageSetting.mimeGroups = readMimeGroupLPw(parser, packageSetting.mimeGroups);
} else if (tagName.equals(TAG_USES_STATIC_LIB)) {
@@ -4212,6 +3969,7 @@ public final class Settings implements Watchable, Snappable {
removeCrossProfileIntentFiltersLPw(userId);
mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
+ mDomainVerificationManager.clearUser(userId);
writePackageListLPr();
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
new file mode 100644
index 000000000000..9588a279ecec
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Stack;
+
+/**
+ * A very specialized serialization/parsing wrapper around {@link TypedXmlSerializer} and {@link
+ * TypedXmlPullParser} intended for use with PackageManager related settings files.
+ * Assumptions/chosen behaviors:
+ * <ul>
+ * <li>No namespace support</li>
+ * <li>Data for a parent object is stored as attributes</li>
+ * <li>All attribute read methods return a default false, -1, or null</li>
+ * <li>Default values will not be written</li>
+ * <li>Children are sub-elements</li>
+ * <li>Collections are repeated sub-elements, no attribute support for collections</li>
+ * </ul>
+ */
+public class SettingsXml {
+
+ private static final String TAG = "SettingsXml";
+
+ private static final boolean DEBUG_THROW_EXCEPTIONS = false;
+
+ private static final String FEATURE_INDENT =
+ "http://xmlpull.org/v1/doc/features.html#indent-output";
+
+ private static final int DEFAULT_NUMBER = -1;
+
+ public static Serializer serializer(TypedXmlSerializer serializer) {
+ return new Serializer(serializer);
+ }
+
+ public static ReadSection parser(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new ReadSectionImpl(parser);
+ }
+
+ public static class Serializer implements AutoCloseable {
+
+ @NonNull
+ private final TypedXmlSerializer mXmlSerializer;
+
+ private final WriteSectionImpl mWriteSection;
+
+ private Serializer(TypedXmlSerializer serializer) {
+ mXmlSerializer = serializer;
+ mWriteSection = new WriteSectionImpl(mXmlSerializer);
+ }
+
+ public WriteSection startSection(@NonNull String sectionName) throws IOException {
+ return mWriteSection.startSection(sectionName);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mWriteSection.closeCompletely();
+ mXmlSerializer.endDocument();
+ }
+ }
+
+ public interface ReadSection extends AutoCloseable {
+
+ @NonNull
+ String getName();
+
+ @NonNull
+ String getDescription();
+
+ boolean has(String attrName);
+
+ @Nullable
+ String getString(String attrName);
+
+ /**
+ * @return value as String or {@param defaultValue} if doesn't exist
+ */
+ @NonNull
+ String getString(String attrName, @NonNull String defaultValue);
+
+ /**
+ * @return value as boolean or false if doesn't exist
+ */
+ boolean getBoolean(String attrName);
+
+ /**
+ * @return value as boolean or {@param defaultValue} if doesn't exist
+ */
+ boolean getBoolean(String attrName, boolean defaultValue);
+
+ /**
+ * @return value as int or {@link #DEFAULT_NUMBER} if doesn't exist
+ */
+ int getInt(String attrName);
+
+ /**
+ * @return value as int or {@param defaultValue} if doesn't exist
+ */
+ int getInt(String attrName, int defaultValue);
+
+ /**
+ * @return value as long or {@link #DEFAULT_NUMBER} if doesn't exist
+ */
+ long getLong(String attrName);
+
+ /**
+ * @return value as long or {@param defaultValue} if doesn't exist
+ */
+ long getLong(String attrName, int defaultValue);
+
+ ChildSection children();
+ }
+
+ /**
+ * <pre><code>
+ * ChildSection child = parentSection.children();
+ * while (child.moveToNext(TAG_CHILD)) {
+ * String readValue = child.getString(...);
+ * ...
+ * }
+ * </code></pre>
+ */
+ public interface ChildSection extends ReadSection {
+ boolean moveToNext();
+
+ boolean moveToNext(@NonNull String expectedChildTagName);
+ }
+
+ public static class ReadSectionImpl implements ChildSection {
+
+ @Nullable
+ private final InputStream mInput;
+
+ @NonNull
+ private final TypedXmlPullParser mParser;
+
+ @NonNull
+ private final Stack<Integer> mDepthStack = new Stack<>();
+
+ public ReadSectionImpl(@NonNull InputStream input)
+ throws IOException, XmlPullParserException {
+ mInput = input;
+ mParser = Xml.newFastPullParser();
+ mParser.setInput(mInput, StandardCharsets.UTF_8.name());
+ moveToFirstTag();
+ }
+
+ public ReadSectionImpl(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ mInput = null;
+ mParser = parser;
+ moveToFirstTag();
+ }
+
+ private void moveToFirstTag() throws IOException, XmlPullParserException {
+ // Move to first tag
+ int type;
+ //noinspection StatementWithEmptyBody
+ while ((type = mParser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @NonNull
+ @Override
+ public String getDescription() {
+ return mParser.getPositionDescription();
+ }
+
+ @Override
+ public boolean has(String attrName) {
+ return mParser.getAttributeValue(null, attrName) != null;
+ }
+
+ @Nullable
+ @Override
+ public String getString(String attrName) {
+ return mParser.getAttributeValue(null, attrName);
+ }
+
+ @NonNull
+ @Override
+ public String getString(String attrName, @NonNull String defaultValue) {
+ String value = mParser.getAttributeValue(null, attrName);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ @Override
+ public boolean getBoolean(String attrName) {
+ return getBoolean(attrName, false);
+ }
+
+ @Override
+ public boolean getBoolean(String attrName, boolean defaultValue) {
+ return mParser.getAttributeBoolean(null, attrName, defaultValue);
+ }
+
+ @Override
+ public int getInt(String attrName) {
+ return getInt(attrName, DEFAULT_NUMBER);
+ }
+
+ @Override
+ public int getInt(String attrName, int defaultValue) {
+ return mParser.getAttributeInt(null, attrName, defaultValue);
+ }
+
+ @Override
+ public long getLong(String attrName) {
+ return getLong(attrName, DEFAULT_NUMBER);
+ }
+
+ @Override
+ public long getLong(String attrName, int defaultValue) {
+ return mParser.getAttributeLong(null, attrName, defaultValue);
+ }
+
+ @Override
+ public ChildSection children() {
+ mDepthStack.push(mParser.getDepth());
+ return this;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return moveToNextInternal(null);
+ }
+
+ @Override
+ public boolean moveToNext(@NonNull String expectedChildTagName) {
+ return moveToNextInternal(expectedChildTagName);
+ }
+
+ private boolean moveToNextInternal(@Nullable String expectedChildTagName) {
+ try {
+ int depth = mDepthStack.peek();
+ boolean hasTag = false;
+ int type;
+ while (!hasTag
+ && (type = mParser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || mParser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (expectedChildTagName != null
+ && !expectedChildTagName.equals(mParser.getName())) {
+ continue;
+ }
+
+ hasTag = true;
+ }
+
+ if (!hasTag) {
+ mDepthStack.pop();
+ }
+
+ return hasTag;
+ } catch (Exception ignored) {
+ return false;
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mDepthStack.isEmpty()) {
+ Slog.wtf(TAG, "Children depth stack was not empty, data may have been lost",
+ new Exception());
+ }
+ if (mInput != null) {
+ mInput.close();
+ }
+ }
+ }
+
+ public interface WriteSection extends AutoCloseable {
+
+ WriteSection startSection(@NonNull String sectionName) throws IOException;
+
+ WriteSection attribute(String attrName, @Nullable String value) throws IOException;
+
+ WriteSection attribute(String attrName, int value) throws IOException;
+
+ WriteSection attribute(String attrName, long value) throws IOException;
+
+ WriteSection attribute(String attrName, boolean value) throws IOException;
+
+ @Override
+ void close() throws IOException;
+
+ void finish() throws IOException;
+ }
+
+ private static class WriteSectionImpl implements WriteSection {
+
+ @NonNull
+ private final TypedXmlSerializer mXmlSerializer;
+
+ @NonNull
+ private final Stack<String> mTagStack = new Stack<>();
+
+ private WriteSectionImpl(@NonNull TypedXmlSerializer xmlSerializer) {
+ mXmlSerializer = xmlSerializer;
+ }
+
+ @Override
+ public WriteSection startSection(@NonNull String sectionName) throws IOException {
+ // Try to start the tag first before we push it to the stack
+ mXmlSerializer.startTag(null, sectionName);
+ mTagStack.push(sectionName);
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, String value) throws IOException {
+ if (value != null) {
+ mXmlSerializer.attribute(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, int value) throws IOException {
+ if (value != DEFAULT_NUMBER) {
+ mXmlSerializer.attributeInt(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, long value) throws IOException {
+ if (value != DEFAULT_NUMBER) {
+ mXmlSerializer.attributeLong(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public WriteSection attribute(String attrName, boolean value) throws IOException {
+ if (value) {
+ mXmlSerializer.attributeBoolean(null, attrName, value);
+ }
+ return this;
+ }
+
+ @Override
+ public void finish() throws IOException {
+ close();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mXmlSerializer.endTag(null, mTagStack.pop());
+ }
+
+ private void closeCompletely() throws IOException {
+ if (DEBUG_THROW_EXCEPTIONS && mTagStack != null && !mTagStack.isEmpty()) {
+ throw new IllegalStateException(
+ "tag stack is not empty when closing, contains " + mTagStack);
+ } else if (mTagStack != null) {
+ while (!mTagStack.isEmpty()) {
+ close();
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
new file mode 100644
index 000000000000..36efb39909a6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Patterns;
+
+import com.android.server.SystemConfig;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.List;
+import java.util.Set;
+
+public class DomainVerificationCollector {
+
+ @NonNull
+ private final PlatformCompat mPlatformCompat;
+
+ @NonNull
+ private final SystemConfig mSystemConfig;
+
+ public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
+ @NonNull SystemConfig systemConfig) {
+ mPlatformCompat = platformCompat;
+ mSystemConfig = systemConfig;
+ }
+
+ /**
+ * With the updated form of the app links verification APIs, an app will be required to declare
+ * domains inside an intent filter which includes all of the following:
+ * <ul>
+ * <li>- android:autoVerify="true"</li>
+ * <li>- Intent.ACTION_VIEW</li>
+ * <li>- Intent.CATEGORY_BROWSABLE</li>
+ * <li>- Intent.CATEGORY_DEFAULT</li>
+ * <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS,
+ * with no other schemes</li>
+ * </ul>
+ *
+ * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other
+ * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly
+ * pretend that all intent filters were set to autoVerify="true".
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long RESTRICT_DOMAINS = 175408749L;
+
+ @NonNull
+ public ArraySet<String> collectAllWebDomains(@NonNull AndroidPackage pkg) {
+ return collectDomains(pkg, false);
+ }
+
+ /**
+ * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires
+ * {@link IntentFilter#getAutoVerify()} == true.
+ */
+ @NonNull
+ public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) {
+ return collectDomains(pkg, true);
+ }
+
+ @NonNull
+ private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
+ boolean checkAutoVerify) {
+ boolean restrictDomains =
+ DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
+
+ ArraySet<String> domains = new ArraySet<>();
+
+ if (restrictDomains) {
+ collectDomains(domains, pkg, checkAutoVerify);
+ } else {
+ collectDomainsLegacy(domains, pkg, checkAutoVerify);
+ }
+
+ return domains;
+ }
+
+ /** @see #RESTRICT_DOMAINS */
+ private void collectDomainsLegacy(@NonNull Set<String> domains,
+ @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ if (!checkAutoVerify) {
+ // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
+ collectDomains(domains, pkg, false);
+ return;
+ }
+
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+
+ // Due to a bug in the platform, for backwards compatibility, assume that all linked apps
+ // require auto verification, even if they forget to mark their manifest as such.
+ boolean needsAutoVerify = mSystemConfig.getLinkedApps().contains(pkg.getPackageName());
+ if (!needsAutoVerify) {
+ for (int activityIndex = 0; activityIndex < activitiesSize && !needsAutoVerify;
+ activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize && !needsAutoVerify;
+ intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ needsAutoVerify = intent.needsVerification();
+ }
+ }
+
+ if (!needsAutoVerify) {
+ return;
+ }
+ }
+
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ if (intent.handlesWebUris(false)) {
+ int authorityCount = intent.countDataAuthorities();
+ for (int index = 0; index < authorityCount; index++) {
+ domains.add(intent.getDataAuthority(index).getHost());
+ }
+ }
+ }
+ }
+ }
+
+ /** @see #RESTRICT_DOMAINS */
+ private void collectDomains(@NonNull Set<String> domains,
+ @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ ParsedActivity activity = activities.get(activityIndex);
+ List<ParsedIntentInfo> intents = activity.getIntents();
+ int intentsSize = intents.size();
+ for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ ParsedIntentInfo intent = intents.get(intentIndex);
+ if (checkAutoVerify && !intent.getAutoVerify()) {
+ continue;
+ }
+
+ if (!intent.hasCategory(Intent.CATEGORY_DEFAULT)
+ || !intent.handlesWebUris(checkAutoVerify)) {
+ continue;
+ }
+
+ // TODO(b/159952358): There seems to be no way to associate the exact host
+ // with its scheme, meaning all hosts have to be verified as if they were
+ // web schemes. This means that given the following:
+ // <intent-filter android:autoVerify="true">
+ // ...
+ // <data android:scheme="https" android:host="one.example.com"/>
+ // <data android:scheme="https" android:host="two.example.com"/>
+ // <data android:host="three.example.com"/>
+ // <data android:scheme="nonWeb" android:host="four.example.com"/>
+ // </intent-filter>
+ // The verification agent will be asked to verify four.example.com, which the
+ // app will probably fail. This can be re-configured to work properly by the
+ // app developer by declaring a separate intent-filter. This may not be worth
+ // fixing.
+ int authorityCount = intent.countDataAuthorities();
+ for (int index = 0; index < authorityCount; index++) {
+ String host = intent.getDataAuthority(index).getHost();
+ // It's easy to misconfigure autoVerify intent filters, so to avoid
+ // adding unintended hosts, check if the host is an HTTP domain.
+ if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+ domains.add(host);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
new file mode 100644
index 000000000000..af9978b91e48
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.PackageUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.Arrays;
+
+public class DomainVerificationDebug {
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ DomainVerificationDebug(DomainVerificationCollector collector) {
+ mCollector = collector;
+ }
+
+ public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId,
+ @NonNull DomainVerificationService.Connection connection,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> stateMap)
+ throws NameNotFoundException {
+ ArrayMap<String, Integer> reusedMap = new ArrayMap<>();
+ ArraySet<String> reusedSet = new ArraySet<>();
+
+ if (packageName == null) {
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = stateMap.valueAt(index);
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = connection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ boolean wasHeaderPrinted = printState(writer, pkgState, pkgSetting.getPkg(),
+ reusedMap, false);
+ printState(writer, pkgState, pkgSetting.getPkg(), userId, reusedSet,
+ wasHeaderPrinted);
+ }
+ } else {
+ DomainVerificationPkgState pkgState = stateMap.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ PackageSetting pkgSetting = connection.getPackageSettingLocked(packageName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ AndroidPackage pkg = pkgSetting.getPkg();
+ printState(writer, pkgState, pkg, reusedMap, false);
+ printState(writer, pkgState, pkg, userId, reusedSet, true);
+ }
+ }
+
+ boolean printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ @NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {
+ reusedMap.clear();
+ reusedMap.putAll(pkgState.getStateMap());
+
+ ArraySet<String> declaredDomains = mCollector.collectAutoVerifyDomains(pkg);
+ int declaredSize = declaredDomains.size();
+ for (int declaredIndex = 0; declaredIndex < declaredSize; declaredIndex++) {
+ String domain = declaredDomains.valueAt(declaredIndex);
+ reusedMap.putIfAbsent(domain, DomainVerificationState.STATE_NO_RESPONSE);
+ }
+
+ boolean printedHeader = false;
+
+ if (!reusedMap.isEmpty()) {
+ if (!wasHeaderPrinted) {
+ Signature[] signatures = pkg.getSigningDetails().signatures;
+ String signaturesDigest = signatures == null ? null : Arrays.toString(
+ PackageUtils.computeSignaturesSha256Digests(
+ pkg.getSigningDetails().signatures));
+
+ writer.println(pkgState.getPackageName() + ":");
+ writer.increaseIndent();
+ writer.println("ID: " + pkgState.getId());
+ writer.println("Signatures: " + signaturesDigest);
+ writer.decreaseIndent();
+ printedHeader = true;
+ }
+
+ writer.increaseIndent();
+ writer.println("Domain verification state:");
+ writer.increaseIndent();
+ int stateSize = reusedMap.size();
+ for (int stateIndex = 0; stateIndex < stateSize; stateIndex++) {
+ String domain = reusedMap.keyAt(stateIndex);
+ Integer state = reusedMap.valueAt(stateIndex);
+ writer.print(domain);
+ writer.print(": ");
+ writer.println(DomainVerificationManager.stateToDebugString(state));
+ }
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ }
+
+ return printedHeader;
+ }
+
+ void printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ @Nullable @UserIdInt Integer userId, @NonNull ArraySet<String> reusedSet,
+ boolean wasHeaderPrinted) {
+ if (userId == null) {
+ return;
+ }
+
+ ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg);
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ if (userId == UserHandle.USER_ALL) {
+ int size = userStates.size();
+ if (size == 0) {
+ printState(writer, pkgState, userId, null, reusedSet, allWebDomains,
+ wasHeaderPrinted);
+ } else {
+ for (int index = 0; index < size; index++) {
+ DomainVerificationUserState userState = userStates.valueAt(index);
+ printState(writer, pkgState, userState.getUserId(), userState, reusedSet,
+ allWebDomains, wasHeaderPrinted);
+ }
+ }
+ } else {
+ DomainVerificationUserState userState = userStates.get(userId);
+ printState(writer, pkgState, userId, userState, reusedSet, allWebDomains,
+ wasHeaderPrinted);
+ }
+ }
+
+ boolean printState(@NonNull IndentingPrintWriter writer,
+ @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
+ @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet,
+ @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) {
+ reusedSet.clear();
+ reusedSet.addAll(allWebDomains);
+ if (userState != null) {
+ reusedSet.removeAll(userState.getEnabledHosts());
+ }
+
+ boolean printedHeader = false;
+
+ ArraySet<String> enabledHosts = userState == null ? null : userState.getEnabledHosts();
+ int enabledSize = CollectionUtils.size(enabledHosts);
+ int disabledSize = reusedSet.size();
+ if (enabledSize > 0 || disabledSize > 0) {
+ if (!wasHeaderPrinted) {
+ writer.println(pkgState.getPackageName() + " " + pkgState.getId() + ":");
+ printedHeader = true;
+ }
+
+ boolean isLinkHandlingAllowed = userState == null
+ || !userState.isDisallowLinkHandling();
+
+ writer.increaseIndent();
+ writer.print("User ");
+ writer.print(userId == UserHandle.USER_ALL ? "all" : userId);
+ writer.println(":");
+ writer.increaseIndent();
+ writer.print("Verification link handling allowed: ");
+ writer.println(isLinkHandlingAllowed);
+ writer.println("Selection state:");
+ writer.increaseIndent();
+
+ if (enabledSize > 0) {
+ writer.println("Enabled:");
+ writer.increaseIndent();
+ for (int enabledIndex = 0; enabledIndex < enabledSize; enabledIndex++) {
+ //noinspection ConstantConditions
+ writer.println(enabledHosts.valueAt(enabledIndex));
+ }
+ writer.decreaseIndent();
+ }
+
+ if (disabledSize > 0) {
+ writer.println("Disabled:");
+ writer.increaseIndent();
+ for (int disabledIndex = 0; disabledIndex < disabledSize; disabledIndex++) {
+ writer.println(reusedSet.valueAt(disabledIndex));
+ }
+ writer.decreaseIndent();
+ }
+
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ writer.decreaseIndent();
+ }
+
+ return printedHeader;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
new file mode 100644
index 000000000000..c521f828ade9
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+public class DomainVerificationEnforcer {
+
+ @NonNull
+ private final Context mContext;
+
+ public DomainVerificationEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Enforced when mutating any state from shell or internally in the system process.
+ */
+ public void assertInternal(int callingUid) {
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ break;
+ default:
+ throw new SecurityException(
+ "Caller " + callingUid + " is not allowed to change internal state");
+ }
+ }
+
+ /**
+ * Enforced when retrieving state for a package. The system, the verifier, and anyone approved
+ * to mutate user selections are allowed through.
+ */
+ public void assertApprovedQuerent(int callingUid, @NonNull DomainVerificationProxy proxy) {
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ break;
+ default:
+ if (!proxy.isCallerVerifier(callingUid)) {
+ mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+ Binder.getCallingPid(), callingUid,
+ "Caller " + callingUid
+ + " is not allowed to query domain verification state");
+ }
+ break;
+ }
+ }
+
+ /**
+ * Enforced when mutating domain verification state inside an exposed API method.
+ */
+ public void assertApprovedVerifier(int callingUid, @NonNull DomainVerificationProxy proxy)
+ throws SecurityException {
+ boolean isAllowed;
+ switch (callingUid) {
+ case Process.ROOT_UID:
+ case Process.SHELL_UID:
+ case Process.SYSTEM_UID:
+ isAllowed = true;
+ break;
+ default:
+ // TODO(b/159952358): Remove permission check? The component package should
+ // have been checked when the verifier component was first scanned in PMS.
+ mContext.enforcePermission(
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+ Binder.getCallingPid(), callingUid,
+ "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
+ isAllowed = proxy.isCallerVerifier(callingUid);
+ break;
+ }
+
+ if (!isAllowed) {
+ throw new SecurityException("Caller " + callingUid
+ + " is not the approved domain verification agent, isVerifier = "
+ + proxy.isCallerVerifier(callingUid));
+ }
+ }
+
+ /**
+ * Enforced when mutating user selection state inside an exposed API method.
+ */
+ public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+ @UserIdInt int targetUserId) throws SecurityException {
+ if (callingUserId != targetUserId) {
+ mContext.enforcePermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit other users");
+ }
+
+ mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit user selections");
+ }
+
+ public void callerIsLegacyUserSelector(int callingUid) {
+ mContext.enforcePermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
+ Binder.getCallingPid(), callingUid,
+ "Caller is not allowed to edit user state");
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
new file mode 100644
index 000000000000..c787356f342c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
@@ -0,0 +1,228 @@
+/*
+ * 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.SettingsXml;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can
+ * be migrated in to the new API. Will throw away the state once it's successfully applied so that
+ * eventually there will be no legacy state on the device.
+ *
+ * This attempt is best effort, and if the legacy state is lost that's acceptable. The user setting
+ * in the legacy API may have been set incorrectly because it was never made obvious to the user
+ * what it actually toggled, so there's a strong argument to prevent migration anyways. The user
+ * can just set their preferences again, this time with finer grained control, if the legacy state
+ * gets dropped.
+ */
+public class DomainVerificationLegacySettings {
+
+ public static final String TAG_DOMAIN_VERIFICATIONS_LEGACY = "domain-verifications-legacy";
+ public static final String TAG_USER_STATES = "user-states";
+ public static final String ATTR_PACKAGE_NAME = "packageName";
+ public static final String TAG_USER_STATE = "user-state";
+ public static final String ATTR_USER_ID = "userId";
+ public static final String ATTR_STATE = "state";
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final ArrayMap<String, LegacyState> mStates = new ArrayMap<>();
+
+ public void add(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info) {
+ synchronized (mLock) {
+ getOrCreateStateLocked(packageName).setInfo(info);
+ }
+ }
+
+ public void add(@NonNull String packageName, @UserIdInt int userId, int state) {
+ synchronized (mLock) {
+ getOrCreateStateLocked(packageName).addUserState(userId, state);
+ }
+ }
+
+ public int getUserState(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null) {
+ return state.getUserState(userId);
+ }
+ }
+ return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ @Nullable
+ public SparseIntArray getUserStates(@NonNull String packageName) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null) {
+ // Yes, this returns outside of the lock, but we assume that retrieval generally
+ // only happens after all adding has concluded from reading settings.
+ return state.getUserStates();
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public IntentFilterVerificationInfo remove(@NonNull String packageName) {
+ synchronized (mLock) {
+ LegacyState state = mStates.get(packageName);
+ if (state != null && !state.isAttached()) {
+ state.markAttached();
+ return state.getInfo();
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private LegacyState getOrCreateStateLocked(@NonNull String packageName) {
+ LegacyState state = mStates.get(packageName);
+ if (state == null) {
+ state = new LegacyState();
+ mStates.put(packageName, state);
+ }
+
+ return state;
+ }
+
+ public void writeSettings(TypedXmlSerializer xmlSerializer) throws IOException {
+ try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+ try (SettingsXml.WriteSection ignored =
+ serializer.startSection(TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+ synchronized (mLock) {
+ final int statesSize = mStates.size();
+ for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+ final LegacyState state = mStates.valueAt(stateIndex);
+ final SparseIntArray userStates = state.getUserStates();
+ if (userStates == null) {
+ continue;
+ }
+
+ final String packageName = mStates.keyAt(stateIndex);
+ try (SettingsXml.WriteSection userStatesSection =
+ serializer.startSection(TAG_USER_STATES)
+ .attribute(ATTR_PACKAGE_NAME, packageName)) {
+ final int userStatesSize = userStates.size();
+ for (int userStateIndex = 0; userStateIndex < userStatesSize;
+ userStateIndex++) {
+ final int userId = userStates.keyAt(userStateIndex);
+ final int userState = userStates.valueAt(userStateIndex);
+ userStatesSection.startSection(TAG_USER_STATE)
+ .attribute(ATTR_USER_ID, userId)
+ .attribute(ATTR_STATE, userState)
+ .finish();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void readSettings(TypedXmlPullParser xmlParser)
+ throws IOException, XmlPullParserException {
+ final SettingsXml.ChildSection child = SettingsXml.parser(xmlParser).children();
+ while (child.moveToNext()) {
+ if (TAG_USER_STATES.equals(child.getName())) {
+ readUserStates(child);
+ }
+ }
+ }
+
+ private void readUserStates(SettingsXml.ReadSection section) {
+ String packageName = section.getString(ATTR_PACKAGE_NAME);
+ synchronized (mLock) {
+ final LegacyState legacyState = getOrCreateStateLocked(packageName);
+ final SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext()) {
+ if (TAG_USER_STATE.equals(child.getName())) {
+ readUserState(child, legacyState);
+ }
+ }
+ }
+ }
+
+ private void readUserState(SettingsXml.ReadSection section, LegacyState legacyState) {
+ int userId = section.getInt(ATTR_USER_ID);
+ int state = section.getInt(ATTR_STATE);
+ legacyState.addUserState(userId, state);
+ }
+
+ static class LegacyState {
+ @Nullable
+ private IntentFilterVerificationInfo mInfo;
+
+ @Nullable
+ private SparseIntArray mUserStates;
+
+ private boolean attached;
+
+ @Nullable
+ public IntentFilterVerificationInfo getInfo() {
+ return mInfo;
+ }
+
+ public int getUserState(int userId) {
+ return mUserStates.get(userId,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+ }
+
+ @Nullable
+ public SparseIntArray getUserStates() {
+ return mUserStates;
+ }
+
+ public void setInfo(@NonNull IntentFilterVerificationInfo info) {
+ mInfo = info;
+ }
+
+ public void addUserState(@UserIdInt int userId, int state) {
+ if (mUserStates == null) {
+ mUserStates = new SparseIntArray(1);
+ }
+ mUserStates.put(userId, state);
+ }
+
+ public boolean isAttached() {
+ return attached;
+ }
+
+ public void markAttached() {
+ attached = true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
new file mode 100644
index 000000000000..7ad275a6f351
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+public interface DomainVerificationManagerInternal extends DomainVerificationManager {
+
+ UUID DISABLED_ID = new UUID(0, 0);
+
+ /**
+ * Generate a new domain set ID to be used for attaching new packages.
+ */
+ @NonNull
+ UUID generateNewId();
+
+ void setConnection(@NonNull Connection connection);
+
+ @NonNull
+ DomainVerificationProxy getProxy();
+
+ /**
+ * Update the proxy implementation that talks to the domain verification agent on device. The
+ * default proxy is a stub that does nothing, and broadcast functionality will only work once a
+ * real implementation is attached.
+ */
+ void setProxy(@NonNull DomainVerificationProxy proxy);
+
+ /**
+ * @see DomainVerificationProxy.BaseConnection#runMessage(int, Object)
+ */
+ boolean runMessage(int messageCode, Object object);
+
+ /**
+ * Restores or creates internal state for the new package. This can either be from scanning a
+ * package at boot, or a truly new installation on the device. It is expected that the {@link
+ * PackageSetting#getDomainSetId()} already be set to the correct value.
+ * <p>
+ * If this is from scan, there should be a pending state that was previous read using {@link
+ * #readSettings(TypedXmlPullParser)}, which will be attached as-is to the package. In this
+ * case, a broadcast will not be sent to the domain verification agent on device, as it is
+ * assumed nothing has changed since the device rebooted.
+ * <p>
+ * If this is a new install, state will be restored from a previous call to {@link
+ * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a
+ * broadcast will be sent to the domain verification agent so it may re-run any verification
+ * logic for the newly associated domains.
+ * <p>
+ * 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.
+ */
+ void addPackage(@NonNull PackageSetting newPkgSetting);
+
+ /**
+ * 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 #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>
+ * 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.
+ */
+ 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.
+ */
+ void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException;
+
+ /**
+ * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
+ * #writeSettings(TypedXmlSerializer)}. 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
+ * device. If restoring from a backup, use {@link #restoreSettings(TypedXmlPullParser)}.
+ */
+ void readSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Read back data from
+ * {@link DomainVerificationLegacySettings#writeSettings(TypedXmlSerializer)}. Assumes that the
+ * {@link DomainVerificationLegacySettings#TAG_DOMAIN_VERIFICATIONS_LEGACY} tag has already
+ * been entered.
+ */
+ void readLegacySettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Remove all state for the given package.
+ */
+ void clearPackage(@NonNull String packageName);
+
+ /**
+ * Delete all the state for a user. This can be because the user has been removed from the
+ * device, or simply that the state for a user should be deleted.
+ */
+ void clearUser(@UserIdInt int userId);
+
+ /**
+ * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
+ * #writeSettings(TypedXmlSerializer)}. Assumes that the
+ * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
+ * tag has already been entered.
+ * <p>
+ * This is <b>only</b> for restore, and will override package states, ignoring if their {@link
+ * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked
+ * as success verify against the server correctly, although the verification agent may decide to
+ * re-verify them when it gets the chance.
+ */
+ /*
+ * TODO(b/170746586): Figure out how to verify that package signatures match at snapshot time
+ * and restore time.
+ */
+ void restoreSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+
+ /**
+ * Set aside a legacy {@link IntentFilterVerificationInfo} that will be restored to a pending
+ * {@link DomainVerificationPkgState} once it's added through
+ * {@link #addPackage(PackageSetting)}.
+ */
+ void addLegacySetting(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info);
+
+ /**
+ * Set aside a legacy user selection that will be restored to a pending
+ * {@link DomainVerificationPkgState} once it's added through
+ * {@link #addPackage(PackageSetting)}.
+ */
+ void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+
+ /**
+ * Until the legacy APIs are entirely removed, returns the legacy state from the previously
+ * written info stored in {@link com.android.server.pm.Settings}.
+ */
+ int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
+
+ /**
+ * Serialize a legacy setting that wasn't attached yet.
+ * TODO: Does this even matter? Should consider for removal.
+ */
+ void writeLegacySettings(TypedXmlSerializer serializer, String name);
+
+ /**
+ * Print the verification state and user selection state of a package.
+ *
+ * @param packageName the package whose state to change, or all packages if none is specified
+ * @param userId the specific user to print, or null to skip printing user selection
+ * states, supports {@link android.os.UserHandle#USER_ALL}
+ */
+ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+
+ @NonNull
+ DomainVerificationShell getShell();
+
+ @NonNull
+ DomainVerificationCollector getCollector();
+
+ /**
+ * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
+ * This can be because the domain was auto-verified for the package, or if the user manually
+ * chose to enable the domain for the package.
+ */
+ boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ @UserIdInt int userId);
+
+ /**
+ * @return the domain verification set ID for the given package, or null if the ID is
+ * unavailable
+ */
+ @Nullable
+ UUID getDomainVerificationInfoId(@NonNull String packageName);
+
+ @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+ void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ @NonNull Set<String> domains, int state)
+ throws IllegalArgumentException, NameNotFoundException;
+
+
+ interface Connection {
+
+ /**
+ * Notify that a settings change has been made and that eventually
+ * {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent.
+ */
+ void scheduleWriteSettings();
+
+ /**
+ * Delegate to {@link Binder#getCallingUid()} to allow mocking in tests.
+ */
+ int getCallingUid();
+
+ /**
+ * Delegate to {@link UserHandle#getCallingUserId()} to allow mocking in tests.
+ */
+ @UserIdInt
+ int getCallingUserId();
+
+ /**
+ * @see DomainVerificationProxy.BaseConnection#schedule(int, java.lang.Object)
+ */
+ void schedule(int code, @Nullable Object object);
+
+ @Nullable
+ PackageSetting getPackageSettingLocked(@NonNull String pkgName);
+
+ @Nullable
+ AndroidPackage getPackageLocked(@NonNull String pkgName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
new file mode 100644
index 000000000000..8aa63372b826
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.ServiceSpecificException;
+import android.util.ArraySet;
+
+import java.util.List;
+import java.util.UUID;
+
+class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
+
+ @NonNull
+ private DomainVerificationService mService;
+
+ DomainVerificationManagerStub(DomainVerificationService service) {
+ mService = service;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ try {
+ return mService.getValidVerificationPackageNames();
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(String packageName) {
+ try {
+ return mService.getDomainVerificationInfo(packageName);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(String domainSetId, List<String> domains,
+ int state) {
+ try {
+ mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
+ new ArraySet<>(domains), state);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed,
+ @UserIdInt int userId) {
+ try {
+ mService.setDomainVerificationLinkHandlingAllowed(packageName, allowed, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(String domainSetId, List<String> domains,
+ boolean enabled, @UserIdInt int userId) {
+ try {
+ mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
+ new ArraySet<>(domains), enabled, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ String packageName, @UserIdInt int userId) {
+ try {
+ return mService.getDomainVerificationUserSelection(packageName, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
+ private RuntimeException rethrow(Exception exception) throws RuntimeException {
+ if (exception instanceof InvalidDomainSetException) {
+ int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
+ packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
+ return new ServiceSpecificException(packedErrorCode,
+ ((InvalidDomainSetException) exception).getPackageName());
+ } else if (exception instanceof NameNotFoundException) {
+ return new ServiceSpecificException(
+ DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND);
+ } else if (exception instanceof RuntimeException) {
+ return (RuntimeException) exception;
+ } else {
+ return new RuntimeException(exception);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
new file mode 100644
index 000000000000..7af78c6a98ca
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
@@ -0,0 +1,36 @@
+/*
+ * 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.verify.domain;
+
+import android.os.Handler;
+
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+/**
+ * Codes that are sent through the {@link PackageManagerService} {@link Handler} and eventually
+ * delegated to {@link DomainVerificationService} and {@link DomainVerificationProxy}.
+ *
+ * These codes are wrapped and thus exclusive to the domain verification APIs. They do not have be
+ * distinct from any of the codes inside {@link PackageManagerService}.
+ */
+public final class DomainVerificationMessageCodes {
+
+ public static final int SEND_REQUEST = 1;
+ public static final int LEGACY_SEND_REQUEST = 2;
+ public static final int LEGACY_ON_INTENT_FILTER_VERIFIED = 3;
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
new file mode 100644
index 000000000000..679f948bb3de
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.SettingsXml;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.UUID;
+
+public class DomainVerificationPersistence {
+
+ private static final String TAG = "DomainVerificationPersistence";
+
+ public static final String TAG_DOMAIN_VERIFICATIONS = "domain-verifications";
+ public static final String TAG_ACTIVE = "active";
+ public static final String TAG_RESTORED = "restored";
+
+ public static final String TAG_PACKAGE_STATE = "package-state";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
+ private static final String TAG_USER_STATES = "user-states";
+
+ public static final String TAG_USER_STATE = "user-state";
+ public static final String ATTR_USER_ID = "userId";
+ public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+ public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
+ public static final String TAG_HOST = "host";
+
+ private static final String TAG_STATE = "state";
+ public static final String TAG_DOMAIN = "domain";
+ public static final String ATTR_NAME = "name";
+ public static final String ATTR_STATE = "state";
+
+ public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException {
+ try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+ try (SettingsXml.WriteSection ignored = serializer.startSection(
+ TAG_DOMAIN_VERIFICATIONS)) {
+ // Both attached and pending states are written to the active set, since both
+ // should be restored when the device reboots or runs a backup. They're merged into
+ // the same list because at read time the distinction isn't relevant. The pending
+ // list should generally be empty at this point anyways.
+ ArraySet<DomainVerificationPkgState> active = new ArraySet<>();
+
+ int attachedSize = attached.size();
+ for (int attachedIndex = 0; attachedIndex < attachedSize; attachedIndex++) {
+ active.add(attached.valueAt(attachedIndex));
+ }
+
+ int pendingSize = pending.size();
+ for (int pendingIndex = 0; pendingIndex < pendingSize; pendingIndex++) {
+ active.add(pending.valueAt(pendingIndex));
+ }
+
+ try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
+ writePackageStates(activeSection, active);
+ }
+
+ try (SettingsXml.WriteSection restoredSection = serializer.startSection(
+ TAG_RESTORED)) {
+ writePackageStates(restoredSection, restored.values());
+ }
+ }
+ }
+ }
+
+ private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
+ @NonNull Collection<DomainVerificationPkgState> states) throws IOException {
+ if (states.isEmpty()) {
+ return;
+ }
+
+ for (DomainVerificationPkgState state : states) {
+ writePkgStateToXml(section, state);
+ }
+ }
+
+ @NonNull
+ public static ReadResult readFromXml(@NonNull TypedXmlPullParser parentParser)
+ throws IOException, XmlPullParserException {
+ ArrayMap<String, DomainVerificationPkgState> active = new ArrayMap<>();
+ ArrayMap<String, DomainVerificationPkgState> restored = new ArrayMap<>();
+
+ SettingsXml.ChildSection child = SettingsXml.parser(parentParser).children();
+ while (child.moveToNext()) {
+ switch (child.getName()) {
+ case TAG_ACTIVE:
+ readPackageStates(child, active);
+ break;
+ case TAG_RESTORED:
+ readPackageStates(child, restored);
+ break;
+ }
+ }
+
+ return new ReadResult(active, restored);
+ }
+
+ private static void readPackageStates(@NonNull SettingsXml.ReadSection section,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> map) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_PACKAGE_STATE)) {
+ DomainVerificationPkgState pkgState = createPkgStateFromXml(child);
+ if (pkgState != null) {
+ // State is unique by package name
+ map.put(pkgState.getPackageName(), pkgState);
+ }
+ }
+ }
+
+ /**
+ * Reads a package state from XML. Assumes the starting {@link #TAG_PACKAGE_STATE} has already
+ * been entered.
+ */
+ @Nullable
+ public static DomainVerificationPkgState createPkgStateFromXml(
+ @NonNull SettingsXml.ReadSection section) {
+ String packageName = section.getString(ATTR_PACKAGE_NAME);
+ String idString = section.getString(ATTR_ID);
+ boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
+ if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
+ return null;
+ }
+ UUID id = UUID.fromString(idString);
+
+ final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
+ final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>();
+
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext()) {
+ switch (child.getName()) {
+ case TAG_STATE:
+ readDomainStates(child, stateMap);
+ break;
+ case TAG_USER_STATES:
+ readUserStates(child, userStates);
+ break;
+ }
+ }
+
+ return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
+ userStates);
+ }
+
+ private static void readUserStates(@NonNull SettingsXml.ReadSection section,
+ @NonNull SparseArray<DomainVerificationUserState> userStates) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_USER_STATE)) {
+ DomainVerificationUserState userState = createUserStateFromXml(child);
+ if (userState != null) {
+ userStates.put(userState.getUserId(), userState);
+ }
+ }
+ }
+
+ private static void readDomainStates(@NonNull SettingsXml.ReadSection stateSection,
+ @NonNull ArrayMap<String, Integer> stateMap) {
+ SettingsXml.ChildSection child = stateSection.children();
+ while (child.moveToNext(TAG_DOMAIN)) {
+ String name = child.getString(ATTR_NAME);
+ int state = child.getInt(ATTR_STATE, DomainVerificationState.STATE_NO_RESPONSE);
+ stateMap.put(name, state);
+ }
+ }
+
+ public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull DomainVerificationPkgState pkgState) throws IOException {
+ try (SettingsXml.WriteSection ignored =
+ parentSection.startSection(TAG_PACKAGE_STATE)
+ .attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName())
+ .attribute(ATTR_ID, pkgState.getId().toString())
+ .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
+ pkgState.isHasAutoVerifyDomains())) {
+ writeStateMap(parentSection, pkgState.getStateMap());
+ writeUserStates(parentSection, pkgState.getUserSelectionStates());
+ }
+ }
+
+ private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull SparseArray<DomainVerificationUserState> states) throws IOException {
+ int size = states.size();
+ if (size == 0) {
+ return;
+ }
+
+ try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
+ for (int index = 0; index < size; index++) {
+ writeUserStateToXml(section, states.valueAt(index));
+ }
+ }
+ }
+
+ private static void writeStateMap(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull ArrayMap<String, Integer> stateMap) throws IOException {
+ if (stateMap.isEmpty()) {
+ return;
+ }
+
+ try (SettingsXml.WriteSection stateSection = parentSection.startSection(TAG_STATE)) {
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ stateSection.startSection(TAG_DOMAIN)
+ .attribute(ATTR_NAME, stateMap.keyAt(index))
+ .attribute(ATTR_STATE, stateMap.valueAt(index))
+ .finish();
+ }
+ }
+ }
+
+ /**
+ * Reads a user state from XML. Assumes the starting {@link #TAG_USER_STATE} has already been
+ * entered.
+ */
+ @Nullable
+ public static DomainVerificationUserState createUserStateFromXml(
+ @NonNull SettingsXml.ReadSection section) {
+ int userId = section.getInt(ATTR_USER_ID);
+ if (userId == -1) {
+ return null;
+ }
+
+ boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+ ArraySet<String> enabledHosts = new ArraySet<>();
+
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_ENABLED_HOSTS)) {
+ readEnabledHosts(child, enabledHosts);
+ }
+
+ return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+ }
+
+ private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
+ @NonNull ArraySet<String> enabledHosts) {
+ SettingsXml.ChildSection child = section.children();
+ while (child.moveToNext(TAG_HOST)) {
+ String hostName = child.getString(ATTR_NAME);
+ if (!TextUtils.isEmpty(hostName)) {
+ enabledHosts.add(hostName);
+ }
+ }
+ }
+
+ public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+ @NonNull DomainVerificationUserState userState) throws IOException {
+ try (SettingsXml.WriteSection section =
+ parentSection.startSection(TAG_USER_STATE)
+ .attribute(ATTR_USER_ID, userState.getUserId())
+ .attribute(ATTR_DISALLOW_LINK_HANDLING,
+ userState.isDisallowLinkHandling())) {
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ if (!enabledHosts.isEmpty()) {
+ try (SettingsXml.WriteSection enabledHostsSection =
+ section.startSection(TAG_ENABLED_HOSTS)) {
+ int size = enabledHosts.size();
+ for (int index = 0; index < size; index++) {
+ enabledHostsSection.startSection(TAG_HOST)
+ .attribute(ATTR_NAME, enabledHosts.valueAt(index))
+ .finish();
+ }
+ }
+ }
+ }
+ }
+
+ public static class ReadResult {
+
+ @NonNull
+ public final ArrayMap<String, DomainVerificationPkgState> active;
+
+ @NonNull
+ public final ArrayMap<String, DomainVerificationPkgState> restored;
+
+ public ReadResult(@NonNull ArrayMap<String, DomainVerificationPkgState> active,
+ @NonNull ArrayMap<String, DomainVerificationPkgState> restored) {
+ this.active = active;
+ this.restored = restored;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
new file mode 100644
index 000000000000..4fd01d903261
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+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.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationService extends SystemService
+ implements DomainVerificationManagerInternal, DomainVerificationShell.Callback {
+
+ private static final String TAG = "DomainVerificationService";
+
+ public static final boolean DEBUG_APPROVAL = true;
+
+ /**
+ * The new user preference API for verifying domains marked autoVerify=true in
+ * AndroidManifest.xml intent filters is not yet implemented in the current platform preview.
+ * This is anticipated to ship before S releases.
+ *
+ * For now, it is possible to preview the new user preference changes by enabling this
+ * ChangeId and using the <code>adb shell pm set-app-links-user-selection</code> and similar
+ * commands.
+ */
+ @ChangeId
+ @Disabled
+ private static final long SETTINGS_API_V2 = 178111421;
+
+ /**
+ * States that are currently alive and attached to a package. Entries are exclusive with the
+ * state stored in {@link DomainVerificationSettings}, as any pending/restored state should be
+ * immediately attached once its available.
+ * <p>
+ * Generally this should be not accessed directly. Prefer calling {@link
+ * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+ *
+ * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final DomainVerificationStateMap<DomainVerificationPkgState> mAttachedPkgStates =
+ new DomainVerificationStateMap<>();
+
+ /**
+ * Lock for all state reads/writes.
+ */
+ private final Object mLock = new Object();
+
+ @NonNull
+ private Connection mConnection;
+
+ @NonNull
+ private final SystemConfig mSystemConfig;
+
+ @NonNull
+ private final PlatformCompat mPlatformCompat;
+
+ @NonNull
+ private final DomainVerificationSettings mSettings;
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ @NonNull
+ private final DomainVerificationEnforcer mEnforcer;
+
+ @NonNull
+ private final DomainVerificationDebug mDebug;
+
+ @NonNull
+ private final DomainVerificationShell mShell;
+
+ @NonNull
+ private final DomainVerificationLegacySettings mLegacySettings;
+
+ @NonNull
+ private final IDomainVerificationManager.Stub mStub = new DomainVerificationManagerStub(this);
+
+ @NonNull
+ private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable();
+
+ public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
+ @NonNull PlatformCompat platformCompat) {
+ super(context);
+ mSystemConfig = systemConfig;
+ mPlatformCompat = platformCompat;
+ mSettings = new DomainVerificationSettings();
+ mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
+ mEnforcer = new DomainVerificationEnforcer(context);
+ mDebug = new DomainVerificationDebug(mCollector);
+ mShell = new DomainVerificationShell(this);
+ mLegacySettings = new DomainVerificationLegacySettings();
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.DOMAIN_VERIFICATION_SERVICE, mStub);
+ }
+
+ @Override
+ public void setConnection(@NonNull Connection connection) {
+ mConnection = connection;
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationProxy getProxy() {
+ return mProxy;
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) {
+ return;
+ }
+
+ verifyPackages(null, false);
+ }
+
+ @Override
+ public void setProxy(@NonNull DomainVerificationProxy proxy) {
+ mProxy = proxy;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getValidVerificationPackageNames() {
+ mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
+ List<String> packageNames = new ArrayList<>();
+ synchronized (mLock) {
+ int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ if (pkgState.isHasAutoVerifyDomains()) {
+ packageNames.add(pkgState.getPackageName());
+ }
+ }
+ }
+ return packageNames;
+ }
+
+ @Nullable
+ @Override
+ public UUID getDomainVerificationInfoId(@NonNull String packageName) {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState != null) {
+ return pkgState.getId();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+ throws NameNotFoundException {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
+ AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+ if (pkg == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
+
+ // TODO(b/159952358): Should the domain list be cached?
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(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);
+ }
+
+ // TODO(b/159952358): Do not return if no values are editable (all ignored states)?
+ return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws InvalidDomainSetException, NameNotFoundException {
+ if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
+ if (state != DomainVerificationState.STATE_SUCCESS) {
+ throw new IllegalArgumentException(
+ "Verifier can only set STATE_SUCCESS or codes greater than or equal to "
+ + "STATE_FIRST_VERIFIER_DEFINED");
+ }
+ }
+
+ setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains,
+ state);
+ }
+
+ @Override
+ public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ @NonNull Set<String> domains, int state)
+ throws InvalidDomainSetException, NameNotFoundException {
+ mEnforcer.assertApprovedVerifier(callingUid, mProxy);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ true /* forAutoVerify */);
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ for (String domain : domains) {
+ Integer previousState = stateMap.get(domain);
+ if (previousState != null
+ && !DomainVerificationManager.isStateModifiable(previousState)) {
+ continue;
+ }
+
+ stateMap.put(domain, state);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+ @Nullable ArraySet<String> domains) throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "State must be one of NO_RESPONSE, SUCCESS, APPROVED, or DENIED");
+ }
+
+ if (packageName == null) {
+ 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);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ AndroidPackage pkg = pkgSetting.getPkg();
+
+ validDomains.clear();
+
+ ArraySet<String> autoVerifyDomains = mCollector.collectAutoVerifyDomains(pkg);
+ if (domains == null) {
+ validDomains.addAll(autoVerifyDomains);
+ } else {
+ validDomains.addAll(domains);
+ validDomains.retainAll(autoVerifyDomains);
+ }
+
+ 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.collectAutoVerifyDomains(pkg);
+ } else {
+ domains.retainAll(mCollector.collectAutoVerifyDomains(pkg));
+ }
+
+ setDomainVerificationStatusInternal(pkgState, state, domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ private void setDomainVerificationStatusInternal(@NonNull DomainVerificationPkgState pkgState,
+ int state, @NonNull ArraySet<String> validDomains) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int size = validDomains.size();
+ for (int index = 0; index < size; index++) {
+ stateMap.put(validDomains.valueAt(index), state);
+ }
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed) throws NameNotFoundException {
+ setDomainVerificationLinkHandlingAllowed(packageName, allowed,
+ mConnection.getCallingUserId());
+ }
+
+ public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), userId);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ if (packageName == null) {
+ synchronized (mLock) {
+ int pkgStateSize = mAttachedPkgStates.size();
+ for (int pkgStateIndex = 0; pkgStateIndex < pkgStateSize; pkgStateIndex++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+ if (userId == UserHandle.USER_ALL) {
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ int userStatesSize = userStates.size();
+ for (int userStateIndex = 0; userStateIndex < userStatesSize;
+ userStateIndex++) {
+ userStates.valueAt(userStateIndex)
+ .setDisallowLinkHandling(!allowed);
+ }
+ } else {
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+ }
+
+ }
+ } else {
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId)
+ .setDisallowLinkHandling(!allowed);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled)
+ throws InvalidDomainSetException, NameNotFoundException {
+ setDomainVerificationUserSelection(domainSetId, domains, enabled,
+ mConnection.getCallingUserId());
+ }
+
+ public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
+ throws InvalidDomainSetException, NameNotFoundException {
+ mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), userId);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ false /* forAutoVerify */);
+ DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+ @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+ throws NameNotFoundException {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+
+ if (packageName == null) {
+ synchronized (mLock) {
+ Set<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);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ continue;
+ }
+
+ validDomains.clear();
+ validDomains.addAll(domains);
+
+ setDomainVerificationUserSelectionInternal(userId, pkgState,
+ pkgSetting.getPkg(), enabled, 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);
+ }
+
+ setDomainVerificationUserSelectionInternal(userId, pkgState, pkgSetting.getPkg(),
+ enabled, domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ private void setDomainVerificationUserSelectionInternal(int userId,
+ @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+ boolean enabled, Set<String> domains) {
+ domains.retainAll(mCollector.collectAllWebDomains(pkg));
+
+ SparseArray<DomainVerificationUserState> userStates =
+ pkgState.getUserSelectionStates();
+ if (userId == UserHandle.USER_ALL) {
+ int size = userStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationUserState userState = userStates.valueAt(index);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+ } else {
+ DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+ if (enabled) {
+ userState.addHosts(domains);
+ } else {
+ userState.removeHosts(domains);
+ }
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName) throws NameNotFoundException {
+ return getDomainVerificationUserSelection(packageName,
+ mConnection.getCallingUserId());
+ }
+
+ @Nullable
+ @Override
+ public DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
+ mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+ mConnection.getCallingUserId(), userId);
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
+ AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+ if (pkg == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+ }
+
+ ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>();
+
+ ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
+ int domainsSize = domains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ hostToUserSelectionMap.put(domains.valueAt(index), false);
+ }
+
+ boolean openVerifiedLinks = false;
+ DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+ if (userState != null) {
+ openVerifiedLinks = !userState.isDisallowLinkHandling();
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ int hostsSize = enabledHosts.size();
+ for (int index = 0; index < hostsSize; index++) {
+ hostToUserSelectionMap.put(enabledHosts.valueAt(index), true);
+ }
+ }
+
+ return new DomainVerificationUserSelection(pkgState.getId(), packageName,
+ UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+ }
+ }
+
+ @NonNull
+ @Override
+ public UUID generateNewId() {
+ // TODO(b/159952358): Domain set ID collisions
+ return UUID.randomUUID();
+ }
+
+ @Override
+ public void migrateState(@NonNull PackageSetting oldPkgSetting,
+ @NonNull PackageSetting newPkgSetting) {
+ String pkgName = newPkgSetting.name;
+ boolean sendBroadcast;
+
+ synchronized (mLock) {
+ UUID oldDomainSetId = oldPkgSetting.getDomainSetId();
+ UUID newDomainSetId = newPkgSetting.getDomainSetId();
+ DomainVerificationPkgState oldPkgState = mAttachedPkgStates.remove(oldDomainSetId);
+
+ AndroidPackage oldPkg = oldPkgSetting.getPkg();
+ AndroidPackage newPkg = newPkgSetting.getPkg();
+
+ ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
+ SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();
+
+ if (oldPkgState == null || oldPkg == null || newPkg == null) {
+ // Should be impossible, but to be safe, continue with a new blank state instead
+ Slog.wtf(TAG, "Invalid state nullability old state = " + oldPkgState
+ + ", old pkgSetting = " + oldPkgSetting
+ + ", new pkgSetting = " + newPkgSetting
+ + ", old pkg = " + oldPkg
+ + ", new pkg = " + newPkg, new Exception());
+
+ DomainVerificationPkgState newPkgState = new DomainVerificationPkgState(
+ pkgName, newDomainSetId, true, newStateMap, newUserStates);
+ mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState);
+ return;
+ }
+
+ ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap();
+ ArraySet<String> newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg);
+ int newDomainsSize = newAutoVerifyDomains.size();
+
+ for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) {
+ String domain = newAutoVerifyDomains.valueAt(newDomainsIndex);
+ Integer oldStateInteger = oldStateMap.get(domain);
+ if (oldStateInteger != null) {
+ int oldState = oldStateInteger;
+ switch (oldState) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_MIGRATED:
+ newStateMap.put(domain, oldState);
+ break;
+ default:
+ // In all other cases, the state code is left unset
+ // (STATE_NO_RESPONSE) to signal to the verification agent that any
+ // existing error has been cleared and the domain should be
+ // re-attempted. This makes update of a package a signal to
+ // re-verify.
+ break;
+ }
+ }
+ }
+
+ SparseArray<DomainVerificationUserState> oldUserStates =
+ oldPkgState.getUserSelectionStates();
+ int oldUserStatesSize = oldUserStates.size();
+ if (oldUserStatesSize > 0) {
+ ArraySet<String> newWebDomains = mCollector.collectAutoVerifyDomains(newPkg);
+ for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
+ oldUserStatesIndex++) {
+ int userId = oldUserStates.keyAt(oldUserStatesIndex);
+ DomainVerificationUserState oldUserState = oldUserStates.valueAt(
+ oldUserStatesIndex);
+ ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
+ ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
+ newEnabledHosts.retainAll(newWebDomains);
+ DomainVerificationUserState newUserState = new DomainVerificationUserState(
+ userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+ newUserStates.put(userId, newUserState);
+ }
+ }
+
+ boolean hasAutoVerifyDomains = newDomainsSize > 0;
+ boolean needsBroadcast =
+ applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains);
+
+ sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
+
+ mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
+ pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates));
+ }
+
+ if (sendBroadcast) {
+ sendBroadcastForPackage(pkgName);
+ }
+ }
+
+ // TODO(b/159952358): Handle valid domainSetIds for PackageSettings with no AndroidPackage
+ @Override
+ public void addPackage(@NonNull PackageSetting newPkgSetting) {
+ // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
+ // the state map, but it would require handling the "migration" case where an app either
+ // gains or loses all domains.
+
+ UUID domainSetId = newPkgSetting.getDomainSetId();
+ String pkgName = newPkgSetting.name;
+
+ boolean sendBroadcast = true;
+
+ DomainVerificationPkgState pkgState;
+ pkgState = mSettings.getPendingState(pkgName);
+ if (pkgState != null) {
+ // Don't send when attaching from pending read, which is usually boot scan. Re-send on
+ // boot is handled in a separate method once all packages are added.
+ sendBroadcast = false;
+ } else {
+ pkgState = mSettings.getRestoredState(pkgName);
+ }
+
+ AndroidPackage pkg = newPkgSetting.getPkg();
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+ boolean hasAutoVerifyDomains = !domains.isEmpty();
+ boolean isPendingOrRestored = pkgState != null;
+ if (isPendingOrRestored) {
+ pkgState.setId(domainSetId);
+ } else {
+ pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
+ }
+
+ boolean needsBroadcast = applyImmutableState(pkgState, domains);
+ if (needsBroadcast && !isPendingOrRestored) {
+ // TODO(b/159952358): Test this behavior
+ // Attempt to preserve user experience by automatically verifying all domains from
+ // legacy state if they were previously approved, or by automatically enabling all
+ // hosts through user selection if legacy state indicates a user previously made the
+ // choice in settings to allow supported links. The domain verification agent should
+ // re-verify these links (set to STATE_MIGRATED) at the next possible opportunity,
+ // and disable them if appropriate.
+ ArraySet<String> webDomains = null;
+
+ SparseIntArray legacyUserStates = mLegacySettings.getUserStates(pkgName);
+ int userStateSize = legacyUserStates == null ? 0 : legacyUserStates.size();
+ for (int index = 0; index < userStateSize; index++) {
+ int userId = legacyUserStates.keyAt(index);
+ int legacyStatus = legacyUserStates.valueAt(index);
+ if (legacyStatus
+ == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ if (webDomains == null) {
+ webDomains = mCollector.collectAllWebDomains(pkg);
+ }
+
+ pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
+ }
+ }
+
+ IntentFilterVerificationInfo legacyInfo = mLegacySettings.remove(pkgName);
+ if (legacyInfo != null
+ && legacyInfo.getStatus()
+ == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int domainsSize = domains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ stateMap.put(domains.valueAt(index), DomainVerificationState.STATE_MIGRATED);
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ mAttachedPkgStates.put(pkgName, domainSetId, pkgState);
+ }
+
+ if (sendBroadcast && hasAutoVerifyDomains) {
+ sendBroadcastForPackage(pkgName);
+ }
+ }
+
+ private boolean applyImmutableState(@NonNull DomainVerificationPkgState pkgState,
+ @NonNull ArraySet<String> autoVerifyDomains) {
+ return applyImmutableState(pkgState.getPackageName(), pkgState.getStateMap(),
+ autoVerifyDomains);
+ }
+
+ /**
+ * Applies any immutable state as the final step when adding or migrating state. Currently only
+ * applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a package.
+ *
+ * @return whether or not a broadcast is necessary for this package
+ */
+ private boolean applyImmutableState(@NonNull String packageName,
+ @NonNull ArrayMap<String, Integer> stateMap,
+ @NonNull ArraySet<String> autoVerifyDomains) {
+ if (mSystemConfig.getLinkedApps().contains(packageName)) {
+ int domainsSize = autoVerifyDomains.size();
+ for (int index = 0; index < domainsSize; index++) {
+ stateMap.put(autoVerifyDomains.valueAt(index),
+ DomainVerificationState.STATE_SYS_CONFIG);
+ }
+ return false;
+ } else {
+ int size = stateMap.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Integer state = stateMap.valueAt(index);
+ // If no longer marked in SysConfig, demote any previous SysConfig state
+ if (state == DomainVerificationState.STATE_SYS_CONFIG) {
+ stateMap.removeAt(index);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
+ synchronized (mLock) {
+ mSettings.writeSettings(serializer, mAttachedPkgStates);
+ }
+
+ mLegacySettings.writeSettings(serializer);
+ }
+
+ @Override
+ public void readSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ mSettings.readSettings(parser, mAttachedPkgStates);
+ }
+ }
+
+ @Override
+ public void readLegacySettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ mLegacySettings.readSettings(parser);
+ }
+
+ @Override
+ public void restoreSettings(@NonNull TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ mSettings.restoreSettings(parser, mAttachedPkgStates);
+ }
+ }
+
+ @Override
+ public void addLegacySetting(@NonNull String packageName,
+ @NonNull IntentFilterVerificationInfo info) {
+ mLegacySettings.add(packageName, info);
+ }
+
+ @Override
+ public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
+ mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+ mLegacySettings.add(packageName, userId, state);
+ }
+
+ @Override
+ public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+ return mLegacySettings.getUserState(packageName, userId);
+ }
+
+ @Override
+ public void writeLegacySettings(TypedXmlSerializer serializer, String name) {
+
+ }
+
+ @Override
+ public void clearPackage(@NonNull String packageName) {
+ synchronized (mLock) {
+ mAttachedPkgStates.remove(packageName);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public void clearUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ int attachedSize = mAttachedPkgStates.size();
+ for (int index = 0; index < attachedSize; index++) {
+ mAttachedPkgStates.valueAt(index).removeUser(userId);
+ }
+
+ mSettings.removeUser(userId);
+ }
+
+ mConnection.scheduleWriteSettings();
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ return mProxy.runMessage(messageCode, object);
+ }
+
+ @Override
+ public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException {
+ synchronized (mLock) {
+ mDebug.printState(writer, packageName, userId, mConnection, mAttachedPkgStates);
+ }
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationShell getShell() {
+ return mShell;
+ }
+
+ @NonNull
+ @Override
+ public DomainVerificationCollector getCollector() {
+ return mCollector;
+ }
+
+ private void sendBroadcastForPackage(@NonNull String packageName) {
+ mProxy.sendBroadcastForPackages(Collections.singleton(packageName));
+ }
+
+ private boolean hasRealVerifier() {
+ return !(mProxy instanceof DomainVerificationProxyUnavailable);
+ }
+
+ /**
+ * Validates parameters provided by an external caller. Checks that an ID is still live and that
+ * any provided domains are valid. Should be called at the beginning of each API that takes in a
+ * {@link UUID} domain set ID.
+ */
+ @GuardedBy("mLock")
+ private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
+ @NonNull Set<String> domains, boolean forAutoVerify)
+ throws InvalidDomainSetException, NameNotFoundException {
+ if (domainSetId == null) {
+ throw new InvalidDomainSetException(null, null,
+ InvalidDomainSetException.REASON_ID_NULL);
+ }
+
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
+ if (pkgState == null) {
+ throw new InvalidDomainSetException(domainSetId, null,
+ InvalidDomainSetException.REASON_ID_INVALID);
+ }
+
+ String pkgName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+ if (pkgSetting == null || pkgSetting.getPkg() == null) {
+ throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
+ }
+
+ if (CollectionUtils.isEmpty(domains)) {
+ throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+ InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY);
+ }
+ AndroidPackage pkg = pkgSetting.getPkg();
+ ArraySet<String> declaredDomains = forAutoVerify
+ ? mCollector.collectAutoVerifyDomains(pkg)
+ : mCollector.collectAllWebDomains(pkg);
+
+ if (domains.retainAll(declaredDomains)) {
+ throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+ InvalidDomainSetException.REASON_UNKNOWN_DOMAIN);
+ }
+
+ return pkgState;
+ }
+
+ @Override
+ public void verifyPackages(@Nullable List<String> packageNames, boolean reVerify) {
+ mEnforcer.assertInternal(mConnection.getCallingUid());
+ Set<String> packagesToBroadcast = new ArraySet<>();
+
+ if (packageNames == null) {
+ synchronized (mLock) {
+ int pkgStatesSize = mAttachedPkgStates.size();
+ for (int pkgStateIndex = 0; pkgStateIndex < pkgStatesSize; pkgStateIndex++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+ addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.get(index);
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState != null) {
+ addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+ }
+ }
+ }
+ }
+
+ if (!packagesToBroadcast.isEmpty()) {
+ mProxy.sendBroadcastForPackages(packagesToBroadcast);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addIfShouldBroadcastLocked(@NonNull Collection<String> packageNames,
+ @NonNull DomainVerificationPkgState pkgState, boolean reVerify) {
+ if ((reVerify && pkgState.isHasAutoVerifyDomains()) || shouldReBroadcastPackage(pkgState)) {
+ packageNames.add(pkgState.getPackageName());
+ }
+ }
+
+ /**
+ * Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}.
+ * Sends only if the only states recorded are default as decided by {@link
+ * DomainVerificationManager#isStateDefault(int)}.
+ *
+ * If any other state is set, it's assumed that the domain verification agent is aware of the
+ * package and has already scheduled future verification requests.
+ */
+ private boolean shouldReBroadcastPackage(DomainVerificationPkgState pkgState) {
+ if (!pkgState.isHasAutoVerifyDomains()) {
+ return false;
+ }
+
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int statesSize = stateMap.size();
+ for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+ Integer state = stateMap.valueAt(stateIndex);
+ if (!DomainVerificationManager.isStateDefault(state)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @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;
+ }
+ 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;
+ }
+ resetDomainState(pkgState, pkgSetting.getPkg());
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset states that are mutable by the domain verification agent.
+ */
+ private void resetDomainState(@NonNull DomainVerificationPkgState pkgState,
+ @NonNull AndroidPackage pkg) {
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ int size = stateMap.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Integer state = stateMap.valueAt(index);
+ boolean reset;
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_RESTORED:
+ reset = true;
+ break;
+ default:
+ reset = state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ break;
+ }
+
+ if (reset) {
+ stateMap.removeAt(index);
+ }
+ }
+
+ applyImmutableState(pkgState, mCollector.collectAutoVerifyDomains(pkg));
+ }
+
+ @Override
+ public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) {
+ 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);
+ if (userId == UserHandle.USER_ALL) {
+ pkgState.removeAllUsers();
+ } else {
+ pkgState.removeUser(userId);
+ }
+ }
+ } else {
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String pkgName = packageNames.get(index);
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+ if (userId == UserHandle.USER_ALL) {
+ pkgState.removeAllUsers();
+ } else {
+ pkgState.removeUser(userId);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+ @UserIdInt int userId) {
+ String packageName = pkgSetting.name;
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "not valid intent");
+ }
+ return false;
+ }
+
+ String host = intent.getData().getHost();
+ final AndroidPackage pkg = pkgSetting.getPkg();
+
+ // Should never be null, but if it is, skip this and assume that v2 is enabled
+ if (pkg != null) {
+ // To allow an instant app to immediately open domains after being installed by the
+ // user, auto approve them for any declared autoVerify domains.
+ if (pkgSetting.getInstantApp(userId)
+ && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+ return true;
+ }
+
+ if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
+ int legacyState = mLegacySettings.getUserState(packageName, userId);
+ switch (legacyState) {
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ // If nothing specifically set, assume v2 rules
+ break;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+ // With v2 split into 2 lists, always and undefined, the concept of whether
+ // or not to ask is irrelevant. Assume the user wants this application to
+ // open the domain.
+ return true;
+ case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+ // Never has the same semantics are before
+ return false;
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+ }
+ return false;
+ }
+
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+ DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+ // Only allow autoVerify approval if the user hasn't disabled it
+ if (userState == null || !userState.isDisallowLinkHandling()) {
+ // Check if the exact host matches
+ Integer state = stateMap.get(host);
+ if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true, "host verified exactly");
+ }
+ return true;
+ }
+
+ // Otherwise see if the host matches a verified domain by wildcard
+ int stateMapSize = stateMap.size();
+ for (int index = 0; index < stateMapSize; index++) {
+ if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+ continue;
+ }
+
+ String domain = stateMap.keyAt(index);
+ if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host verified by wildcard");
+ }
+ return true;
+ }
+ }
+ }
+
+ // Check user state if available
+ if (userState == null) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "userState unavailable");
+ }
+ return false;
+ }
+
+ // See if the user has approved the exact host
+ ArraySet<String> enabledHosts = userState.getEnabledHosts();
+ if (enabledHosts.contains(host)) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host enabled by user exactly");
+ }
+ return true;
+ }
+
+ // See if the host matches a user selection by wildcard
+ int enabledHostsSize = enabledHosts.size();
+ for (int index = 0; index < enabledHostsSize; index++) {
+ String domain = enabledHosts.valueAt(index);
+ if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, true,
+ "host enabled by user through wildcard");
+ }
+ return true;
+ }
+ }
+
+ if (DEBUG_APPROVAL) {
+ debugApproval(packageName, intent, userId, false, "not approved");
+ }
+ return false;
+ }
+ }
+
+ private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+ @UserIdInt int userId, boolean approved, @NonNull String reason) {
+ String approvalString = approved ? "approved" : "denied";
+ Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
+ + " for user " + userId + ": " + reason);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
new file mode 100644
index 000000000000..073967e00134
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+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.Pair;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class DomainVerificationSettings {
+
+ /**
+ * 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
+ * package scan completes.
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<String, DomainVerificationPkgState> mPendingPkgStates = new ArrayMap<>();
+
+ /**
+ * States from restore that have yet to attach to a package. These are special in that their IDs
+ * are dropped when the package is installed/otherwise becomes available, because the ID will
+ * not match if the data is restored from a different device install.
+ * <p>
+ * If multiple restore calls come in and they overlap, the latest entry added for a package name
+ * will be taken, dropping any previous versions.
+ **/
+ @GuardedBy("mLock")
+ @NonNull
+ private final ArrayMap<String, DomainVerificationPkgState> mRestoredPkgStates =
+ new ArrayMap<>();
+
+ /**
+ * Lock for all state reads/writes.
+ */
+ private final Object mLock = new Object();
+
+
+ public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException {
+ synchronized (mLock) {
+ DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
+ mPendingPkgStates, mRestoredPkgStates);
+ }
+ }
+
+ /**
+ * 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)} on the same device setup.
+ */
+ public void readSettings(@NonNull TypedXmlPullParser parser,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException, XmlPullParserException {
+ DomainVerificationPersistence.ReadResult result =
+ DomainVerificationPersistence.readFromXml(parser);
+ ArrayMap<String, DomainVerificationPkgState> active = result.active;
+ ArrayMap<String, DomainVerificationPkgState> restored = result.restored;
+
+ synchronized (mLock) {
+ int activeSize = active.size();
+ for (int activeIndex = 0; activeIndex < activeSize; activeIndex++) {
+ DomainVerificationPkgState pkgState = active.valueAt(activeIndex);
+ String pkgName = pkgState.getPackageName();
+ DomainVerificationPkgState existingState = liveState.get(pkgName);
+ if (existingState != null) {
+ // 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);
+ }
+ } else {
+ mPendingPkgStates.put(pkgName, pkgState);
+ }
+ }
+
+ int restoredSize = restored.size();
+ for (int restoredIndex = 0; restoredIndex < restoredSize; restoredIndex++) {
+ DomainVerificationPkgState pkgState = restored.valueAt(restoredIndex);
+ mRestoredPkgStates.put(pkgState.getPackageName(), pkgState);
+ }
+ }
+ }
+
+ /**
+ * Parses a previously stored set of states and merges them with {@param liveState}, directly
+ * mutating the values. This is intended for restoration across device setups.
+ */
+ public void restoreSettings(@NonNull TypedXmlPullParser parser,
+ @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+ throws IOException, XmlPullParserException {
+ // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
+ // a new device.
+
+ DomainVerificationPersistence.ReadResult result =
+ DomainVerificationPersistence.readFromXml(parser);
+
+ // When restoring settings, both active and previously restored are merged, since they
+ // should both go into the newly restored data. Active is added on top of restored just
+ // in case a duplicate is found. Active should be preferred.
+ ArrayMap<String, DomainVerificationPkgState> stateList = result.restored;
+ stateList.putAll(result.active);
+
+ synchronized (mLock) {
+ for (int stateIndex = 0; stateIndex < stateList.size(); stateIndex++) {
+ DomainVerificationPkgState newState = stateList.valueAt(stateIndex);
+ String pkgName = newState.getPackageName();
+ DomainVerificationPkgState existingState = liveState.get(pkgName);
+ if (existingState == null) {
+ existingState = mPendingPkgStates.get(pkgName);
+ }
+ if (existingState == null) {
+ existingState = mRestoredPkgStates.get(pkgName);
+ }
+
+ if (existingState != null) {
+ mergePkgState(existingState, newState);
+ } 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
+ // installed. This means coercing STATE_SUCCESS and STATE_RESTORED to
+ // STATE_RESTORED and dropping everything else, the same logic that
+ // mergePkgState runs, without the merge part.
+ ArrayMap<String, Integer> stateMap = newState.getStateMap();
+ int size = stateMap.size();
+ for (int index = 0; index < size; index++) {
+ Integer stateInteger = stateMap.valueAt(index);
+ if (stateInteger != null) {
+ int state = stateInteger;
+ if (state == DomainVerificationState.STATE_SUCCESS
+ || state == DomainVerificationState.STATE_RESTORED) {
+ stateMap.setValueAt(index, state);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Merges a newly restored state with existing state. This should only be called for restore,
+ * when the IDs aren't required to match.
+ * <p>
+ * If the existing state for a domain is
+ * {@link DomainVerificationState#STATE_NO_RESPONSE}, then it will be overridden with
+ * {@link DomainVerificationState#STATE_RESTORED} if the restored state is
+ * {@link DomainVerificationState#STATE_SUCCESS} or
+ * {@link DomainVerificationState#STATE_RESTORED}.
+ * <p>
+ * Otherwise the existing state is preserved, assuming any system rules, success state, or
+ * 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.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void mergePkgState(@NonNull DomainVerificationPkgState oldState,
+ @NonNull DomainVerificationPkgState newState) {
+ 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) {
+ // Cannot add domains to an app
+ continue;
+ }
+
+ int oldStateCode = oldStateCodeInteger;
+ if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
+ if (newStateCode == DomainVerificationState.STATE_SUCCESS
+ || newStateCode == DomainVerificationState.STATE_RESTORED) {
+ oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED);
+ }
+ }
+ }
+
+ SparseArray<DomainVerificationUserState> oldSelectionStates =
+ oldState.getUserSelectionStates();
+
+ SparseArray<DomainVerificationUserState> newSelectionStates =
+ newState.getUserSelectionStates();
+
+ DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM);
+ if (newUserState != null) {
+ ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
+ DomainVerificationUserState oldUserState =
+ oldSelectionStates.get(UserHandle.USER_SYSTEM);
+
+ boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+ if (oldUserState == null) {
+ oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
+ newEnabledHosts, disallowLinkHandling);
+ oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
+ } else {
+ oldUserState.addHosts(newEnabledHosts)
+ .setDisallowLinkHandling(disallowLinkHandling);
+ }
+ }
+ }
+
+ public void removeUser(@UserIdInt int userId) {
+ int pendingSize = mPendingPkgStates.size();
+ for (int index = 0; index < pendingSize; index++) {
+ mPendingPkgStates.valueAt(index).removeUser(userId);
+ }
+
+ // TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
+ // on a new device
+ int restoredSize = mRestoredPkgStates.size();
+ for (int index = 0; index < restoredSize; index++) {
+ mRestoredPkgStates.valueAt(index).removeUser(userId);
+ }
+ }
+
+ @Nullable
+ public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
+ synchronized (mLock) {
+ return mPendingPkgStates.get(pkgName);
+ }
+ }
+
+ @Nullable
+ public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
+ synchronized (mLock) {
+ return mRestoredPkgStates.get(pkgName);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
new file mode 100644
index 000000000000..7f9e75aa2926
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class DomainVerificationShell {
+
+ @NonNull
+ private final Callback mCallback;
+
+ public DomainVerificationShell(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ public void printHelp(@NonNull PrintWriter pw) {
+ pw.println(" get-app-links [--user <USER_ID>] [<PACKAGE>]");
+ pw.println(" Prints the domain verification state for the given package, or for all");
+ pw.println(" packages if none is specified.");
+ pw.println(" --user <USER_ID>: include user selections (includes all domains, not");
+ pw.println(" just autoVerify ones)");
+ pw.println(" reset-app-links [--user <USER_ID>] [<PACKAGE>]");
+ pw.println(" Resets domain verification state for the given package, or for all");
+ pw.println(" packages if none is specified.");
+ pw.println(" --user <USER_ID>: clear user selection state instead; note this means");
+ pw.println(" domain verification state will NOT be cleared");
+ pw.println(" <PACKAGE>: the package to reset, or \"all\" to reset all packages");
+ pw.println(" verify-app-links [--re-verify] [<PACKAGE>]");
+ pw.println(" Broadcasts a verification request for the given package, or for all");
+ pw.println(" packages if none is specified. Only sends if the package has previously");
+ pw.println(" not recorded a response.");
+ pw.println(" --re-verify: send even if the package has recorded a response");
+ pw.println(" set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...");
+ pw.println(" Manually set the state of a domain for a package. The domain must be");
+ pw.println(" declared by the package as autoVerify for this to work. This command");
+ pw.println(" will not report a failure for domains that could not be applied.");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" <STATE>: the code to set the domains to, valid values are:");
+ pw.println(" STATE_NO_RESPONSE (0): reset as if no response was ever recorded.");
+ pw.println(" STATE_SUCCESS (1): treat domain as successfully verified by domain.");
+ pw.println(" verification agent. Note that the domain verification agent can");
+ pw.println(" override this.");
+ pw.println(" STATE_APPROVED (2): treat domain as always approved, preventing the");
+ pw.println(" domain verification agent from changing it.");
+ pw.println(" STATE_DENIED (3): treat domain as always denied, preveting the domain");
+ pw.println(" verification agent from changing it.");
+ pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
+ pw.println(" change every domain.");
+ pw.println(" set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>]");
+ pw.println(" <ENABLED> <DOMAINS>...");
+ pw.println(" Manually set the state of a host user selection for a package. The domain");
+ pw.println(" must be declared by the package for this to work. This command will not");
+ pw.println(" report a failure for domains that could not be applied.");
+ pw.println(" --user <USER_ID>: the user to change selections for");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" <ENABLED>: whether or not to approve the domain");
+ pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
+ pw.println(" change every domain.");
+ pw.println(" set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
+ pw.println(" <ENABLED> <DOMAINS>...");
+ pw.println(" Toggle the auto verified link handling setting for a package.");
+ pw.println(" --user <USER_ID>: the user to change selections for");
+ pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+ pw.println(" packages will be reset if no one package is specified.");
+ pw.println(" <ALLOWED>: true to allow the package to open auto verified links, false");
+ pw.println(" to disable");
+ }
+
+ /**
+ * Run a shell/debugging command.
+ *
+ * @return null if the command is unhandled, true if the command succeeded, false if it failed
+ */
+ public Boolean runCommand(@NonNull BasicShellCommandHandler commandHandler,
+ @NonNull String command) {
+ switch (command) {
+ case "get-app-links":
+ return runGetAppLinks(commandHandler);
+ case "reset-app-links":
+ return runResetAppLinks(commandHandler);
+ case "verify-app-links":
+ return runVerifyAppLinks(commandHandler);
+ case "set-app-links":
+ return runSetAppLinks(commandHandler);
+ case "set-app-links-user-selection":
+ return runSetAppLinksUserSelection(commandHandler);
+ case "set-app-links-allowed":
+ return runSetAppLinksAllowed(commandHandler);
+ }
+
+ return null;
+ }
+
+
+ // pm set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...
+ private boolean runSetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ String packageName = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--package")) {
+ packageName = commandHandler.getNextArgRequired();
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ String state = commandHandler.getNextArgRequired();
+ int stateInt;
+ switch (state) {
+ case "STATE_NO_RESPONSE":
+ case "0":
+ stateInt = DomainVerificationState.STATE_NO_RESPONSE;
+ break;
+ case "STATE_SUCCESS":
+ case "1":
+ stateInt = DomainVerificationState.STATE_SUCCESS;
+ break;
+ case "STATE_APPROVED":
+ case "2":
+ stateInt = DomainVerificationState.STATE_APPROVED;
+ break;
+ case "STATE_DENIED":
+ case "3":
+ stateInt = DomainVerificationState.STATE_DENIED;
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println("Invalid state option: " + state);
+ return false;
+ }
+
+ ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+ if (domains.isEmpty()) {
+ commandHandler.getErrPrintWriter().println("No domains specified");
+ return false;
+ }
+
+ if (domains.size() == 1 && domains.contains("all")) {
+ domains = null;
+ }
+
+ try {
+ mCallback.setDomainVerificationStatusInternal(packageName, stateInt,
+ domains);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>...
+ private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+ String packageName = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ switch (option) {
+ case "--user":
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ break;
+ case "--package":
+ packageName = commandHandler.getNextArgRequired();
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ if (userId == null) {
+ commandHandler.getErrPrintWriter().println("Error: User ID not specified");
+ return false;
+ }
+
+ userId = translateUserId(userId, "runSetAppLinksUserSelection");
+
+ String enabledString = commandHandler.getNextArgRequired();
+
+ // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
+ // accidentally parsed as a boolean
+ boolean enabled;
+ switch (enabledString) {
+ case "true":
+ enabled = true;
+ break;
+ case "false":
+ enabled = false;
+ break;
+ default:
+ commandHandler.getErrPrintWriter().println(
+ "Invalid enabled param: " + enabledString);
+ return false;
+ }
+
+ ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+ if (domains.isEmpty()) {
+ commandHandler.getErrPrintWriter().println("No domains specified");
+ return false;
+ }
+
+ try {
+ mCallback.setDomainVerificationUserSelectionInternal(userId,
+ packageName, enabled, domains);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ // pm get-app-links [--user <USER_ID>] [<PACKAGE>]
+ private boolean runGetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ userId = userId == null ? null : translateUserId(userId, "runGetAppLinks");
+
+ String packageName = commandHandler.getNextArg();
+
+ try (IndentingPrintWriter writer = new IndentingPrintWriter(
+ commandHandler.getOutPrintWriter(), /* singleIndent */ " ", /* wrapLength */
+ 120)) {
+ writer.increaseIndent();
+ try {
+ mCallback.printState(writer, packageName, userId);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println(
+ "Error: package " + packageName + " unavailable");
+ return false;
+ }
+ writer.decreaseIndent();
+ return true;
+ }
+ }
+
+ // pm reset-app-links [--user USER_ID] [<PACKAGE>]
+ private boolean runResetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ Integer userId = null;
+
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ userId = userId == null ? null : translateUserId(userId, "runResetAppLinks");
+
+ List<String> packageNames;
+ String pkgNameArg = commandHandler.peekNextArg();
+ if (TextUtils.isEmpty(pkgNameArg)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (pkgNameArg.equalsIgnoreCase("all")) {
+ packageNames = null;
+ } else {
+ packageNames = Arrays.asList(commandHandler.peekRemainingArgs());
+ }
+
+ if (userId != null) {
+ mCallback.clearUserSelections(packageNames, userId);
+ } else {
+ mCallback.clearDomainVerificationState(packageNames);
+ }
+
+ return true;
+ }
+
+ // pm verify-app-links [--re-verify] [<PACKAGE>]
+ private boolean runVerifyAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+ boolean reVerify = false;
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--re-verify")) {
+ reVerify = true;
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+ return false;
+ }
+ }
+
+ List<String> packageNames = null;
+ String pkgNameArg = commandHandler.getNextArg();
+ if (!TextUtils.isEmpty(pkgNameArg)) {
+ packageNames = Collections.singletonList(pkgNameArg);
+ }
+
+ mCallback.verifyPackages(packageNames, reVerify);
+
+ return true;
+ }
+
+ // pm set-app-links-allowed [--package <PACKAGE>] [--user <USER_ID>] <ALLOWED>
+ private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) {
+ String packageName = null;
+ Integer userId = null;
+ Boolean allowed = null;
+ String option;
+ while ((option = commandHandler.getNextOption()) != null) {
+ if (option.equals("--package")) {
+ packageName = commandHandler.getNextArgRequired();
+ } if (option.equals("--user")) {
+ userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+ } else if (allowed == null) {
+ allowed = Boolean.valueOf(option);
+ } else {
+ commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option);
+ return false;
+ }
+ }
+
+ if (TextUtils.isEmpty(packageName)) {
+ commandHandler.getErrPrintWriter().println("Error: no package specified");
+ return false;
+ } else if (packageName.equalsIgnoreCase("all")) {
+ packageName = null;
+ }
+
+ if (userId == null) {
+ commandHandler.getErrPrintWriter().println("Error: user ID not specified");
+ return false;
+ }
+
+ if (allowed == null) {
+ commandHandler.getErrPrintWriter().println("Error: allowed setting not specified");
+ return false;
+ }
+
+ userId = translateUserId(userId, "runSetAppLinksAllowed");
+
+ try {
+ mCallback.setDomainVerificationLinkHandlingAllowedInternal(packageName, allowed,
+ userId);
+ } catch (NameNotFoundException e) {
+ commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+ return false;
+ }
+
+ return true;
+ }
+
+ private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) {
+ ArrayList<String> args = new ArrayList<>();
+ String arg;
+ while ((arg = commandHandler.getNextArg()) != null) {
+ args.add(arg);
+ }
+ return args;
+ }
+
+ private int translateUserId(@UserIdInt int userId, @NonNull String logContext) {
+ return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, true, logContext, "pm command");
+ }
+
+ /**
+ * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are
+ * even more internal, and so that testing is easier.
+ */
+ public interface Callback {
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the state for a domain.
+ *
+ * @param packageName the package whose state to change, or all packages if none is
+ * specified
+ * @param state the new state code, valid values are
+ * {@link DomainVerificationState#STATE_NO_RESPONSE},
+ * {@link DomainVerificationState#STATE_SUCCESS}, {@link
+ * DomainVerificationState#STATE_APPROVED}, and {@link
+ * DomainVerificationState#STATE_DENIED}
+ * @param domains the set of domains to change, or null to change all of them
+ */
+ void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+ @Nullable ArraySet<String> domains) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the state for a domain.
+ *
+ * @param packageName the package whose state to change, or all packages if non is
+ * specified
+ * @param enabled whether the domain is now approved by the user
+ * @param domains the set of domains to change
+ */
+ void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+ @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * @see DomainVerificationManager#getDomainVerificationUserSelection(String)
+ */
+ @Nullable
+ DomainVerificationUserSelection getDomainVerificationUserSelection(
+ @NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+ * the setting for a package.
+ *
+ * @param packageName the package whose state to change, or all packages if non is
+ * specified
+ * @param allowed whether the package is allowed to automatically open links through
+ * domain verification
+ */
+ void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+ boolean allowed, @UserIdInt int userId) throws NameNotFoundException;
+
+ /**
+ * Reset all the domain verification states for all domains for the given package names, or
+ * all package names if null is provided.
+ */
+ void clearDomainVerificationState(@Nullable List<String> packageNames);
+
+ /**
+ * Reset all the user selections for the given package names, or all package names if null
+ * is provided.
+ */
+ void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId);
+
+ /**
+ * Broadcast a verification request for the given package names, or all package names if
+ * null is provided. By default only re-broadcasts if a package has not recorded a
+ * response.
+ *
+ * @param reVerify send even if the package has previously recorded a response
+ */
+ void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
+
+ /**
+ * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer)
+ */
+ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+ @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
new file mode 100644
index 000000000000..474f822d6a73
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.verify.domain;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+final class DomainVerificationUtils {
+
+ /**
+ * Consolidates package exception messages. A generic unavailable message is included since the
+ * caller doesn't bother to check why the package isn't available.
+ */
+ @CheckResult
+ static NameNotFoundException throwPackageUnavailable(@NonNull String packageName)
+ throws NameNotFoundException {
+ throw new NameNotFoundException("Package " + packageName + " unavailable");
+ }
+
+ static boolean isDomainVerificationIntent(Intent intent) {
+ return intent.isWebIntent()
+ && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+ && intent.hasCategory(Intent.CATEGORY_DEFAULT);
+ }
+
+ static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
+ long changeId) {
+ //noinspection ConstantConditions
+ return Binder.withCleanCallingIdentity(
+ () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+ }
+
+ /**
+ * Passed to {@link PlatformCompat} because this can be invoked mid-install process or when
+ * {@link PackageManagerService#mLock} is being held, and {@link PlatformCompat} will not be
+ * able to query the pending {@link ApplicationInfo} from {@link PackageManager}.
+ * <p>
+ * TODO(b/177613575): Can a different API be used?
+ */
+ @NonNull
+ private static ApplicationInfo buildMockAppInfo(@NonNull AndroidPackage pkg) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = pkg.getPackageName();
+ appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
+ return appInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 000000000000..c6c979107e75
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
new file mode 100644
index 000000000000..48099aa5382b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2020 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.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * State for a single package for the domain verification APIs. Stores the state of each individual
+ * domain declared by the package, including its verification state and user selection state.
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class DomainVerificationPkgState {
+
+ @NonNull
+ private final String mPackageName;
+
+ @NonNull
+ private UUID mId;
+
+ /**
+ * Whether or not the package declares any autoVerify domains. This is separate from an empty
+ * check on the map itself, because an empty map means no response recorded, not necessarily no
+ * domains declared. When this is false, {@link #mStateMap} will be empty, but
+ * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to
+ * allow this package to open, which may or may not be marked autoVerify.
+ */
+ private final boolean mHasAutoVerifyDomains;
+
+ /**
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @NonNull
+ private final ArrayMap<String, Integer> mStateMap;
+
+ @NonNull
+ private final SparseArray<DomainVerificationUserState> mUserSelectionStates;
+
+ public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
+ boolean hasAutoVerifyDomains) {
+ this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
+ }
+
+ @Nullable
+ public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) {
+ return mUserSelectionStates.get(userId);
+ }
+
+ @Nullable
+ public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) {
+ DomainVerificationUserState userState = mUserSelectionStates.get(userId);
+ if (userState == null) {
+ userState = new DomainVerificationUserState(userId);
+ mUserSelectionStates.put(userId, userState);
+ }
+ return userState;
+ }
+
+ public void setId(@NonNull UUID id) {
+ mId = id;
+ }
+
+ public void removeUser(@UserIdInt int userId) {
+ mUserSelectionStates.remove(userId);
+ }
+
+ public void removeAllUsers() {
+ mUserSelectionStates.clear();
+ }
+
+ private int userSelectionStatesHashCode() {
+ return mUserSelectionStates.contentHashCode();
+ }
+
+ private boolean userSelectionStatesEquals(
+ @NonNull SparseArray<DomainVerificationUserState> other) {
+ return mUserSelectionStates.contentEquals(other);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationPkgState.
+ *
+ * @param stateMap
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationPkgState(
+ @NonNull String packageName,
+ @NonNull UUID id,
+ boolean hasAutoVerifyDomains,
+ @NonNull ArrayMap<String,Integer> stateMap,
+ @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) {
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mId = id;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mId);
+ this.mHasAutoVerifyDomains = hasAutoVerifyDomains;
+ this.mStateMap = stateMap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mStateMap);
+ this.mUserSelectionStates = userSelectionStates;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mUserSelectionStates);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull UUID getId() {
+ return mId;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isHasAutoVerifyDomains() {
+ return mHasAutoVerifyDomains;
+ }
+
+ /**
+ * Map of domains to state integers. Only domains that are not set to the default value of
+ * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+ *
+ * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+ * such as storing no state when the package is marked as a linked app in SystemConfig.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArrayMap<String,Integer> getStateMap() {
+ return mStateMap;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() {
+ return mUserSelectionStates;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationPkgState { " +
+ "packageName = " + mPackageName + ", " +
+ "id = " + mId + ", " +
+ "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
+ "stateMap = " + mStateMap + ", " +
+ "userSelectionStates = " + mUserSelectionStates +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationPkgState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationPkgState that = (DomainVerificationPkgState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mId, that.mId)
+ && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
+ && Objects.equals(mStateMap, that.mStateMap)
+ && userSelectionStatesEquals(that.mUserSelectionStates);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Objects.hashCode(mId);
+ _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
+ _hash = 31 * _hash + Objects.hashCode(mStateMap);
+ _hash = 31 * _hash + userSelectionStatesHashCode();
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1608234185474L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic void setId(java.util.UUID)\npublic void removeUser(int)\npublic void removeAllUsers()\nprivate int userSelectionStatesHashCode()\nprivate boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
new file mode 100644
index 000000000000..88ccd83899c6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.verify.domain.models;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A feature specific implementation of a multi-key map, since lookups by both a {@link String}
+ * package name and {@link UUID} domain set ID should be supported.
+ *
+ * @param <ValueType> stored object type
+ */
+public class DomainVerificationStateMap<ValueType> {
+
+ private static final String TAG = "DomainVerificationStateMap";
+
+ @NonNull
+ private final ArrayMap<String, ValueType> mPackageNameMap = new ArrayMap<>();
+
+ @NonNull
+ private final ArrayMap<UUID, ValueType> mDomainSetIdMap = new ArrayMap<>();
+
+ public int size() {
+ return mPackageNameMap.size();
+ }
+
+ @NonNull
+ public ValueType valueAt(@IntRange(from = 0) int index) {
+ return mPackageNameMap.valueAt(index);
+ }
+
+ @Nullable
+ public ValueType get(@NonNull String packageName) {
+ return mPackageNameMap.get(packageName);
+ }
+
+ @Nullable
+ public ValueType get(@NonNull UUID domainSetId) {
+ return mDomainSetIdMap.get(domainSetId);
+ }
+
+ public void put(@NonNull String packageName, @NonNull UUID domainSetId,
+ @NonNull ValueType valueType) {
+ if (mPackageNameMap.containsKey(packageName)) {
+ remove(packageName);
+ }
+
+ mPackageNameMap.put(packageName, valueType);
+ mDomainSetIdMap.put(domainSetId, valueType);
+ }
+
+ @Nullable
+ public ValueType remove(@NonNull String packageName) {
+ ValueType valueRemoved = mPackageNameMap.remove(packageName);
+ if (valueRemoved != null) {
+ int index = mDomainSetIdMap.indexOfValue(valueRemoved);
+ if (index >= 0) {
+ mDomainSetIdMap.removeAt(index);
+ }
+ }
+ return valueRemoved;
+ }
+
+ @Nullable
+ public ValueType remove(@NonNull UUID domainSetId) {
+ ValueType valueRemoved = mDomainSetIdMap.remove(domainSetId);
+ if (valueRemoved != null) {
+ int index = mPackageNameMap.indexOfValue(valueRemoved);
+ if (index >= 0) {
+ mPackageNameMap.removeAt(index);
+ }
+ }
+ return valueRemoved;
+ }
+
+ @NonNull
+ public List<String> getPackageNames() {
+ return new ArrayList<>(mPackageNameMap.keySet());
+ }
+
+ /**
+ * Exposes the backing values collection of the one of the internal maps. Should only be used
+ * for test assertions.
+ */
+ @VisibleForTesting
+ public Collection<ValueType> values() {
+ return new ArrayList<>(mPackageNameMap.values());
+ }
+
+ @Override
+ public String toString() {
+ return "DomainVerificationStateMap{"
+ + "packageNameMap=" + mPackageNameMap
+ + ", domainSetIdMap=" + mDomainSetIdMap
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
new file mode 100644
index 000000000000..8e8260899a48
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 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.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+
+/**
+ * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
+ * that domain when a web URL Intent is sent ft.
+ */
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+public class DomainVerificationUserState {
+
+ @UserIdInt
+ private final int mUserId;
+
+ /** List of domains which have been enabled by the user. **/
+ @NonNull
+ private final ArraySet<String> mEnabledHosts;
+
+ /** Whether to disallow this package from automatically opening links by auto verification. */
+ private boolean mDisallowLinkHandling;
+
+ public DomainVerificationUserState(@UserIdInt int userId) {
+ mUserId = userId;
+ mEnabledHosts = new ArraySet<>();
+ }
+
+ public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) {
+ mEnabledHosts.addAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) {
+ mEnabledHosts.addAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
+ mEnabledHosts.removeAll(newHosts);
+ return this;
+ }
+
+ public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) {
+ mEnabledHosts.removeAll(newHosts);
+ return this;
+ }
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainVerificationUserState.
+ *
+ * @param enabledHosts
+ * List of domains which have been enabled by the user. *
+ */
+ @DataClass.Generated.Member
+ public DomainVerificationUserState(
+ @UserIdInt int userId,
+ @NonNull ArraySet<String> enabledHosts,
+ boolean disallowLinkHandling) {
+ this.mUserId = userId;
+ com.android.internal.util.AnnotationValidations.validate(
+ UserIdInt.class, null, mUserId);
+ this.mEnabledHosts = enabledHosts;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mEnabledHosts);
+ this.mDisallowLinkHandling = disallowLinkHandling;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
+ /**
+ * List of domains which have been enabled by the user. *
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArraySet<String> getEnabledHosts() {
+ return mEnabledHosts;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isDisallowLinkHandling() {
+ return mDisallowLinkHandling;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
+ mDisallowLinkHandling = value;
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainVerificationUserState { " +
+ "userId = " + mUserId + ", " +
+ "enabledHosts = " + mEnabledHosts + ", " +
+ "disallowLinkHandling = " + mDisallowLinkHandling +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainVerificationUserState that = (DomainVerificationUserState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mUserId == that.mUserId
+ && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
+ && mDisallowLinkHandling == that.mDisallowLinkHandling;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mUserId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
+ _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1608234273324L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
+ inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
new file mode 100644
index 000000000000..715d8fb0fc2d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
@@ -0,0 +1,125 @@
+/*
+ * 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.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(b/170321181): Combine the proxy versions for supporting v1 and v2 at once
+public interface DomainVerificationProxy {
+
+ String TAG = "DomainVerificationProxy";
+
+ boolean DEBUG_PROXIES = false;
+
+ static <ConnectionType extends DomainVerificationProxyV1.Connection
+ & DomainVerificationProxyV2.Connection> DomainVerificationProxy makeProxy(
+ @Nullable ComponentName componentV1, @Nullable ComponentName componentV2,
+ @NonNull Context context, @NonNull DomainVerificationManagerInternal manager,
+ @NonNull DomainVerificationCollector collector, @NonNull ConnectionType connection) {
+ if (DEBUG_PROXIES) {
+ Slog.d(TAG, "Intent filter verification agent: " + componentV1);
+ Slog.d(TAG, "Domain verification agent: " + componentV2);
+ }
+
+ if (componentV2 != null && componentV1 != null
+ && !Objects.equals(componentV2.getPackageName(), componentV1.getPackageName())) {
+ // Only allow a legacy verifier if it's in the same package as the v2 verifier
+ componentV1 = null;
+ }
+
+ DomainVerificationProxy proxyV1 = null;
+ DomainVerificationProxy proxyV2 = null;
+
+ if (componentV1 != null) {
+ proxyV1 = new DomainVerificationProxyV1(context, manager, collector, connection,
+ componentV1);
+ }
+
+ if (componentV2 != null) {
+ proxyV2 = new DomainVerificationProxyV2(context, connection, componentV2);
+ }
+
+ if (proxyV1 != null && proxyV2 != null) {
+ return new DomainVerificationProxyCombined(proxyV1, proxyV2);
+ }
+
+ if (proxyV1 != null) {
+ return proxyV1;
+ }
+
+ if (proxyV2 != null) {
+ return proxyV2;
+ }
+
+ return new DomainVerificationProxyUnavailable();
+ }
+
+ default void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ }
+
+ /**
+ * Runs a message on the caller's Handler as a result of {@link BaseConnection#schedule(int,
+ * Object)}. Abstracts the actual scheduling/running from the manager class. This is also
+ * necessary so that different what codes can be used depending on the verifier proxy on device,
+ * to allow backporting v1. The backport proxy may schedule more or less messages than the v2
+ * proxy.
+ *
+ * @param messageCode One of the values in {@link DomainVerificationMessageCodes}.
+ * @param object Arbitrary object that was originally included.
+ */
+ default boolean runMessage(int messageCode, Object object) {
+ return false;
+ }
+
+ default boolean isCallerVerifier(int callingUid) {
+ return false;
+ }
+
+ @Nullable
+ default ComponentName getComponentName() {
+ return null;
+ }
+
+ interface BaseConnection {
+
+ /**
+ * Schedule something to be run later. The implementation is left up to the caller.
+ *
+ * @param code One of the values in {@link DomainVerificationMessageCodes}.
+ * @param object Arbitrary object to include with the message.
+ */
+ void schedule(int code, @Nullable Object object);
+
+ long getPowerSaveTempWhitelistAppDuration();
+
+ DeviceIdleInternal getDeviceIdleInternal();
+
+ boolean isCallerPackage(int callingUid, @NonNull String packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
new file mode 100644
index 000000000000..8571c08699bd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.verify.domain.proxy;
+
+import android.annotation.NonNull;
+
+import java.util.Set;
+
+class DomainVerificationProxyCombined implements DomainVerificationProxy {
+
+ @NonNull
+ private final DomainVerificationProxy mProxyV1;
+ @NonNull
+ private final DomainVerificationProxy mProxyV2;
+
+ DomainVerificationProxyCombined(@NonNull DomainVerificationProxy proxyV1,
+ @NonNull DomainVerificationProxy proxyV2) {
+ mProxyV1 = proxyV1;
+ mProxyV2 = proxyV2;
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ mProxyV2.sendBroadcastForPackages(packageNames);
+ mProxyV1.sendBroadcastForPackages(packageNames);
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ // Both proxies must run, so cannot use a direct ||, which may skip the right hand side
+ boolean resultV2 = mProxyV2.runMessage(messageCode, object);
+ boolean resultV1 = mProxyV1.runMessage(messageCode, object);
+ return resultV2 || resultV1;
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mProxyV2.isCallerVerifier(callingUid) || mProxyV1.isCallerVerifier(callingUid);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
new file mode 100644
index 000000000000..bd77983256c5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
@@ -0,0 +1,21 @@
+/*
+ * 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.verify.domain.proxy;
+
+/** Stub implementation for when the verification agent is unavailable */
+public class DomainVerificationProxyUnavailable implements DomainVerificationProxy {
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
new file mode 100644
index 000000000000..eab89e987885
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -0,0 +1,257 @@
+/*
+ * 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.verify.domain.proxy;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationProxyV1 implements DomainVerificationProxy {
+
+ private static final String TAG = "DomainVerificationProxyV1";
+
+ private static final boolean DEBUG_BROADCASTS = false;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Connection mConnection;
+
+ @NonNull
+ private final ComponentName mVerifierComponent;
+
+ @NonNull
+ private final DomainVerificationManagerInternal mManager;
+
+ @NonNull
+ private final DomainVerificationCollector mCollector;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Pair<UUID, String>> mRequests = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private int mVerificationToken = 0;
+
+ public DomainVerificationProxyV1(@NonNull Context context,
+ @NonNull DomainVerificationManagerInternal manager,
+ @NonNull DomainVerificationCollector collector, @NonNull Connection connection,
+ @NonNull ComponentName verifierComponent) {
+ mContext = context;
+ mConnection = connection;
+ mVerifierComponent = verifierComponent;
+ mManager = manager;
+ mCollector = collector;
+ }
+
+ public static void queueLegacyVerifyResult(@NonNull Context context,
+ @NonNull DomainVerificationProxyV1.Connection connection, int verificationId,
+ int verificationCode, @Nullable List<String> failedDomains, int callingUid) {
+ context.enforceCallingOrSelfPermission(
+ Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ "Only the intent filter verification agent can verify applications");
+
+ connection.schedule(DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED,
+ new Response(callingUid, verificationId, verificationCode, failedDomains));
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ synchronized (mLock) {
+ int size = mRequests.size();
+ for (int index = size - 1; index >= 0; index--) {
+ Pair<UUID, String> pair = mRequests.valueAt(index);
+ if (packageNames.contains(pair.second)) {
+ mRequests.removeAt(index);
+ }
+ }
+ }
+ mConnection.schedule(DomainVerificationMessageCodes.LEGACY_SEND_REQUEST, packageNames);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ switch (messageCode) {
+ case DomainVerificationMessageCodes.LEGACY_SEND_REQUEST:
+ @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG, "Requesting domain verification for " + packageNames);
+ }
+
+ ArrayMap<Integer, Pair<UUID, String>> newRequests = new ArrayMap<>(
+ packageNames.size());
+ synchronized (mLock) {
+ for (String packageName : packageNames) {
+ UUID domainSetId = mManager.getDomainVerificationInfoId(packageName);
+ if (domainSetId == null) {
+ continue;
+ }
+
+ newRequests.put(mVerificationToken++,
+ Pair.create(domainSetId, packageName));
+ }
+ mRequests.putAll(newRequests);
+ }
+
+ sendBroadcasts(newRequests);
+ return true;
+ case DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED:
+ Response response = (Response) object;
+
+ Pair<UUID, String> pair = mRequests.get(response.verificationId);
+ if (pair == null) {
+ return true;
+ }
+
+ UUID domainSetId = pair.first;
+ String packageName = pair.second;
+ DomainVerificationInfo set;
+ try {
+ set = mManager.getDomainVerificationInfo(packageName);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return true;
+ }
+
+ if (!Objects.equals(domainSetId, set.getIdentifier())) {
+ return true;
+ }
+
+ Set<String> successfulDomains = new ArraySet<>(set.getHostToStateMap().keySet());
+ successfulDomains.removeAll(response.failedDomains);
+
+ int callingUid = response.callingUid;
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ successfulDomains, DomainVerificationState.STATE_SUCCESS);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException ignored) {
+ }
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ new ArraySet<>(response.failedDomains),
+ DomainVerificationState.STATE_LEGACY_FAILURE);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException ignored) {
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private void sendBroadcasts(@NonNull ArrayMap<Integer, Pair<UUID, String>> verifications) {
+ final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+ mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+ mVerifierComponent.getPackageName(), allowListTimeout,
+ UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+ int size = verifications.size();
+ for (int index = 0; index < size; index++) {
+ int verificationId = verifications.keyAt(index);
+ String packageName = verifications.valueAt(index).second;
+ AndroidPackage pkg = mConnection.getPackage(packageName);
+
+ String hostsString = buildHostsString(pkg);
+
+ Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+ .setComponent(mVerifierComponent)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
+ verificationId)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
+ IntentFilter.SCHEME_HTTPS)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
+ hostsString)
+ .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
+ packageName)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(allowListTimeout);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+ }
+ }
+
+ @NonNull
+ private String buildHostsString(@NonNull AndroidPackage pkg) {
+ // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
+ // not the version of the verification agent on device.
+ ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+ return TextUtils.join(" ", domains);
+ }
+
+ private static class Response {
+ public final int callingUid;
+ public final int verificationId;
+ public final int verificationCode;
+ @NonNull
+ public final List<String> failedDomains;
+
+ private Response(int callingUid, int verificationId, int verificationCode,
+ @Nullable List<String> failedDomains) {
+ this.callingUid = callingUid;
+ this.verificationId = verificationId;
+ this.verificationCode = verificationCode;
+ this.failedDomains = failedDomains == null ? Collections.emptyList() : failedDomains;
+ }
+ }
+
+ public interface Connection extends BaseConnection {
+
+ @Nullable
+ AndroidPackage getPackage(@NonNull String packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
new file mode 100644
index 000000000000..9fcbce2ad055
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
@@ -0,0 +1,106 @@
+/*
+ * 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.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationRequest;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+
+import java.util.Set;
+
+public class DomainVerificationProxyV2 implements DomainVerificationProxy {
+
+ private static final String TAG = "DomainVerificationProxyV2";
+
+ private static final boolean DEBUG_BROADCASTS = true;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Connection mConnection;
+
+ @NonNull
+ private final ComponentName mVerifierComponent;
+
+ public DomainVerificationProxyV2(@NonNull Context context, @NonNull Connection connection,
+ @NonNull ComponentName verifierComponent) {
+ mContext = context;
+ mConnection = connection;
+ mVerifierComponent = verifierComponent;
+ }
+
+ @Override
+ public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+ mConnection.schedule(com.android.server.pm.verify.domain.DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
+ }
+
+ @Override
+ public boolean runMessage(int messageCode, Object object) {
+ switch (messageCode) {
+ case DomainVerificationMessageCodes.SEND_REQUEST:
+ @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+ DomainVerificationRequest request = new DomainVerificationRequest(packageNames);
+
+ final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(allowListTimeout);
+
+ mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+ mVerifierComponent.getPackageName(), allowListTimeout,
+ UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+ Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+ .setComponent(mVerifierComponent)
+ .putExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST, request)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG, "Requesting domain verification for " + packageNames);
+ }
+
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCallerVerifier(int callingUid) {
+ return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+ }
+
+ @Nullable
+ @Override
+ public ComponentName getComponentName() {
+ return mVerifierComponent;
+ }
+
+ public interface Connection extends BaseConnection {
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 636be4a359a1..50cb00f1887f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -160,6 +160,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.dex.SystemServerDexLoadReporter;
+import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.policy.PermissionPolicyService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.policy.role.RoleServicePlatformHelperImpl;
@@ -1060,11 +1061,18 @@ public final class SystemServer implements Dumpable {
SystemClock.elapsedRealtime());
}
+ t.traceBegin("StartDomainVerificationService");
+ DomainVerificationService domainVerificationService = new DomainVerificationService(
+ mSystemContext, SystemConfig.getInstance(), platformCompat);
+ mSystemServiceManager.startService(domainVerificationService);
+ t.traceEnd();
+
t.traceBegin("StartPackageManagerService");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
- mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
+ domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
+ mOnlyCore);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 21c863dde3f6..6c5c1d4b59eb 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -54,6 +54,7 @@ import org.mockito.Mockito.same
import org.mockito.Mockito.verify
import org.testng.Assert.assertThrows
import java.io.File
+import java.util.UUID
@RunWith(Parameterized::class)
class PackageManagerComponentLabelIconOverrideTest {
@@ -262,8 +263,13 @@ class PackageManagerComponentLabelIconOverrideTest {
.apply(block)
.hideAsFinal()
- private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
- null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+ private fun makePkgSetting(pkgName: String) = spy(
+ PackageSetting(
+ pkgName, null, File("/test"),
+ null, null, null, null, 0, 0, 0, 0, null, null, null,
+ UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
+ )
+ ) {
this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
}
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
new file mode 100644
index 000000000000..4aa8abc84392
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 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.
+
+android_test {
+ name: "PackageManagerServiceUnitTests",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "junit",
+ "services.core",
+ "servicestests-utils",
+ "testng",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
new file mode 100644
index 000000000000..2ef7a1f56e76
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.pm.test"
+ />
+
+</manifest>
+
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
new file mode 100644
index 000000000000..78dd1c5f6990
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<configuration description="Test module config for PackageManagerServiceUnitTests">
+ <option name="test-tag" value="PackageManagerServiceUnitTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageManagerServiceUnitTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.pm.test" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
new file mode 100644
index 000000000000..e99b07144853
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2020 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 android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Build
+import android.os.PatternMatcher
+import android.util.ArraySet
+import com.android.server.SystemConfig
+import com.android.server.compat.PlatformCompat
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+
+class DomainVerificationCollectorTest {
+
+ companion object {
+ private const val TEST_PKG_NAME = "com.test.pkg"
+ }
+
+ private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
+ whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+ (arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
+ }
+ }
+
+ @Test
+ fun verifyV1() {
+ val pkg = mockPkg(useV2 = false, autoVerify = true)
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+ }
+
+ @Test
+ fun verifyV1NoAutoVerify() {
+ val pkg = mockPkg(useV2 = false, autoVerify = false)
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV1ForceAutoVerify() {
+ val pkg = mockPkg(useV2 = false, autoVerify = false)
+ val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+ }
+
+ @Test
+ fun verifyV1NoValidIntentFilter() {
+ val pkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { TEST_PKG_NAME }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.R }
+
+ val activityList = listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example1.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(true)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+
+ // The presence of a non-web-scheme as the only autoVerify
+ // intent-filter, when non-forced, means that v1 will not pick
+ // up the package for verification.
+ addDataScheme("nonWebScheme")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
+ )
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+
+ val collector = mockCollector()
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV2() {
+ val pkg = mockPkg(useV2 = true, autoVerify = true)
+ val collector = mockCollector()
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg))
+ .containsExactly("example1.com", "example3.com")
+ }
+
+ @Test
+ fun verifyV2NoAutoVerify() {
+ val pkg = mockPkg(useV2 = true, autoVerify = false)
+ val collector = mockCollector()
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ @Test
+ fun verifyV2ForceAutoVerifyIgnored() {
+ val pkg = mockPkg(useV2 = true, autoVerify = false)
+ val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+
+ assertThat(collector.collectAllWebDomains(pkg))
+ .containsExactly("example1.com", "example2.com", "example3.com")
+ assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+ }
+
+ private fun mockCollector(linkedApps: Set<String> = emptySet()): DomainVerificationCollector {
+ val systemConfig = mockThrowOnUnmocked<SystemConfig> {
+ whenever(this.linkedApps) { ArraySet(linkedApps) }
+ }
+
+ return DomainVerificationCollector(platformCompat, systemConfig)
+ }
+
+ private fun mockPkg(useV2: Boolean, autoVerify: Boolean): AndroidPackage {
+ // Translate equivalent of the following manifest declaration. This string isn't actually
+ // parsed, but it's a far easier to read representation of the test data.
+ // language=XML
+ """
+ <xml>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub"/>
+ <data android:host="example1.com"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="http"/>
+ <data android:path="/sub2"/>
+ <data android:host="example2.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub3"/>
+ <data android:host="example3.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub4"/>
+ <data android:host="example4.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub5"/>
+ <data android:host="example5.com"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ <data android:path="/sub5"/>
+ <data android:host="example5.com"/>
+ </intent-filter>
+ </xml>
+ """.trimIndent()
+
+ return mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { TEST_PKG_NAME }
+ whenever(targetSdkVersion) {
+ if (useV2) Build.VERSION_CODES.S else Build.VERSION_CODES.R
+ }
+
+ // The intents are split into separate Activities to test that multiple are collected
+ val activityList = listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example1.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub3", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example3.com", null)
+ }
+ )
+ },
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addDataScheme("https")
+ addDataPath("/sub4", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example4.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub5", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example5.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataPath("/sub6", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example6.com", null)
+ }
+ )
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
new file mode 100644
index 000000000000..deb314764404
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.UserHandle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.UUID
+
+@RunWith(Parameterized::class)
+class DomainVerificationCoreApiTest {
+
+ companion object {
+ private val IS_EQUAL_TO: (value: Any, other: Any) -> Unit = { value, other ->
+ assertThat(value).isEqualTo(other)
+ }
+ private val IS_MAP_EQUAL_TO: (value: Map<*, *>, other: Map<*, *>) -> Unit = { value,
+ other ->
+ assertThat(value).containsExactlyEntriesIn(other)
+ }
+
+ @JvmStatic
+ @Parameterized.Parameters
+ fun parameters() = arrayOf(
+ Parameter(
+ initial = {
+ DomainVerificationRequest(
+ setOf(
+ "com.test.pkg.one",
+ "com.test.pkg.two"
+ )
+ )
+ },
+ unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationRequest, Set<String>>(first, second,
+ { it.packageNames }, { it.component1() }) { value, other ->
+ assertThat(value).containsExactlyElementsIn(other)
+ }
+ }
+ ),
+ Parameter(
+ initial = {
+ DomainVerificationInfo(
+ UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+ "com.test.pkg",
+ mapOf(
+ "example.com" to 0,
+ "example.org" to 1,
+ "example.new" to 1000
+ )
+ )
+ },
+ unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationInfo, UUID>(first, second,
+ { it.identifier }, { it.component1() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationInfo, String>(first, second,
+ { it.packageName }, { it.component2() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationInfo, Map<String, Int?>>(first, second,
+ { it.hostToStateMap }, { it.component3() }, IS_MAP_EQUAL_TO
+ )
+ }
+ ),
+ Parameter(
+ initial = {
+ DomainVerificationUserSelection(
+ UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+ "com.test.pkg",
+ UserHandle.of(10),
+ true,
+ mapOf(
+ "example.com" to true,
+ "example.org" to false,
+ "example.new" to true
+ )
+ )
+ },
+ unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainVerificationUserSelection, UUID>(first, second,
+ { it.identifier }, { it.component1() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, String>(first, second,
+ { it.packageName }, { it.component2() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, UserHandle>(first, second,
+ { it.user }, { it.component3() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, Boolean>(
+ first, second, { it.isLinkHandlingAllowed },
+ { it.component4() }, IS_EQUAL_TO
+ )
+ assertAll<DomainVerificationUserSelection, Map<String, Boolean>>(
+ first, second, { it.hostToUserSelectionMap },
+ { it.component5() }, IS_MAP_EQUAL_TO
+ )
+ }
+ )
+ )
+
+ class Parameter<T : Parcelable>(
+ val initial: () -> T,
+ val unparcel: (Parcel) -> T,
+ private val assertion: (first: T, second: T) -> Unit
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ fun assert(first: Any, second: Any) = assertion(first as T, second as T)
+ }
+
+ private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) {
+ values.indices.drop(1).forEach {
+ block(values[0], values[it])
+ }
+ }
+
+ private fun <T, V : Any> assertAll(
+ first: T,
+ second: T,
+ fieldValue: (T) -> V,
+ componentValue: (T) -> V,
+ assertion: (value: V, other: V) -> Unit
+ ) {
+ val values = arrayOf<Any>(fieldValue(first), fieldValue(second),
+ componentValue(first), componentValue(second))
+ values.indices.drop(1).forEach {
+ @Suppress("UNCHECKED_CAST")
+ assertion(values[0] as V, values[it] as V)
+ }
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var parameter: Parameter<*>
+
+ @Test
+ fun parcel() {
+ val parcel = Parcel.obtain()
+ val initial = parameter.initial()
+ initial.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+
+ val newInitial = parameter.initial()
+ val unparceled = parameter.unparcel(parcel)
+ parameter.assert(newInitial, unparceled)
+
+ assertAll(initial, newInitial, unparceled) { value: Any, other: Any ->
+ assertThat(value).isEqualTo(other)
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
new file mode 100644
index 000000000000..d863194d6889
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -0,0 +1,478 @@
+/*
+ * 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 android.content.Context
+import android.content.Intent
+import android.content.pm.PackageUserState
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Build
+import android.os.Process
+import android.util.ArraySet
+import android.util.Singleton
+import android.util.SparseArray
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.verify.domain.DomainVerificationEnforcer
+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
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spyThrowOnUnmocked
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.testng.Assert.assertThrows
+import java.io.File
+import java.util.UUID
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+
+private typealias Enforcer = DomainVerificationEnforcer
+
+@RunWith(Parameterized::class)
+class DomainVerificationEnforcerTest {
+
+ val context: Context = InstrumentationRegistry.getInstrumentation().context
+
+ companion object {
+ private val INTERNAL_UIDS = listOf(Process.ROOT_UID, Process.SHELL_UID, Process.SYSTEM_UID)
+ private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1
+ private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2
+
+ private const val TEST_PKG = "com.test"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Any> {
+ val makeEnforcer: (Context) -> DomainVerificationEnforcer = {
+ DomainVerificationEnforcer(it)
+ }
+
+ val mockPkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { TEST_PKG }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+ whenever(activities) {
+ listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ addDataAuthority("example.com", null)
+ }
+ )
+ }
+ )
+ }
+ }
+
+ val uuid = UUID.randomUUID()
+
+ // TODO: PackageSetting field encapsulation to move to whenever(name)
+ val mockPkgSetting = spyThrowOnUnmocked(
+ PackageSetting(
+ TEST_PKG,
+ TEST_PKG,
+ File("/test"),
+ null,
+ null,
+ null,
+ null,
+ 1,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ uuid
+ )
+ ) {
+ whenever(getPkg()) { mockPkg }
+ whenever(domainSetId) { uuid }
+ whenever(userState) {
+ SparseArray<PackageUserState>().apply {
+ this[0] = PackageUserState()
+ }
+ }
+ }
+
+ val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> =
+ {
+ val callingUidInt = AtomicInteger(-1)
+ val callingUserIdInt = AtomicInteger(-1)
+ Triple(
+ callingUidInt, callingUserIdInt, DomainVerificationService(
+ it,
+ mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+ mockThrowOnUnmocked {
+ whenever(
+ isChangeEnabled(
+ anyLong(),
+ any()
+ )
+ ) { true }
+ }).apply {
+ setConnection(mockThrowOnUnmocked {
+ whenever(callingUid) { callingUidInt.get() }
+ whenever(callingUserId) { callingUserIdInt.get() }
+ whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting }
+ whenever(getPackageLocked(TEST_PKG)) { mockPkg }
+ whenever(schedule(anyInt(), any()))
+ whenever(scheduleWriteSettings())
+ })
+ }
+ )
+ }
+
+ fun enforcer(
+ type: Type,
+ name: String,
+ block: DomainVerificationEnforcer.(
+ callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
+ ) -> Unit
+ ) = Params(
+ type,
+ makeEnforcer,
+ name
+ ) { enforcer, callingUid, callingUserId, userId, proxy ->
+ enforcer.block(callingUid, callingUserId, userId, proxy)
+ }
+
+ fun service(
+ type: Type,
+ name: String,
+ block: DomainVerificationService.(
+ callingUid: Int, callingUserId: Int, userId: Int
+ ) -> Unit
+ ) = Params(
+ type,
+ makeService,
+ name
+ ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy ->
+ val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService
+ callingUidInt.set(callingUid)
+ callingUserIdInt.set(callingUserId)
+ service.setProxy(proxy)
+ service.addPackage(mockPkgSetting)
+ service.block(callingUid, callingUserId, userId)
+ }
+
+ return arrayOf(
+ enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
+ assertInternal(callingUid)
+ },
+ enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
+ assertApprovedQuerent(callingUid, proxy)
+ },
+ enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
+ assertApprovedVerifier(callingUid, proxy)
+ },
+ enforcer(
+ Type.SELECTOR,
+ "approvedUserSelector"
+ ) { callingUid, callingUserId, userId, _ ->
+ assertApprovedUserSelector(callingUid, callingUserId, userId)
+ },
+
+ service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+ setDomainVerificationStatusInternal(
+ TEST_PKG,
+ DomainVerificationManager.STATE_SUCCESS,
+ ArraySet(setOf("example.com"))
+ )
+ },
+ service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+ setDomainVerificationUserSelectionInternal(
+ userId,
+ TEST_PKG,
+ false,
+ ArraySet(setOf("example.com"))
+ )
+ },
+ service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
+ verifyPackages(listOf(TEST_PKG), true)
+ },
+ service(Type.INTERNAL, "clearState") { _, _, _ ->
+ clearDomainVerificationState(listOf(TEST_PKG))
+ },
+ service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
+ clearUserSelections(listOf(TEST_PKG), userId)
+ },
+ service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+ validVerificationPackageNames
+ },
+ service(Type.QUERENT, "getInfo") { _, _, _ ->
+ getDomainVerificationInfo(TEST_PKG)
+ },
+ service(Type.VERIFIER, "setStatus") { _, _, _ ->
+ setDomainVerificationStatus(
+ uuid,
+ setOf("example.com"),
+ DomainVerificationManager.STATE_SUCCESS
+ )
+ },
+ service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+ setDomainVerificationStatusInternal(
+ callingUid,
+ uuid,
+ setOf("example.com"),
+ DomainVerificationManager.STATE_SUCCESS
+ )
+ },
+ service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
+ setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+ },
+ service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
+ setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+ },
+ service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
+ getDomainVerificationUserSelection(TEST_PKG)
+ },
+ service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
+ getDomainVerificationUserSelection(TEST_PKG, userId)
+ },
+ service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
+ setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+ },
+ service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
+ setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+ },
+ )
+ }
+
+ data class Params<T : Any>(
+ val type: Type,
+ val construct: (context: Context) -> T,
+ val name: String,
+ private val method: (
+ T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
+ ) -> Unit
+ ) {
+ override fun toString() = "${type}_$name"
+
+ fun runMethod(
+ target: Any,
+ callingUid: Int,
+ callingUserId: Int,
+ userId: Int,
+ proxy: DomainVerificationProxy
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ method(target as T, callingUid, callingUserId, userId, proxy)
+ }
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var params: Params<*>
+
+ private val proxy: DomainVerificationProxy = mockThrowOnUnmocked {
+ whenever(isCallerVerifier(VERIFIER_UID)) { true }
+ whenever(isCallerVerifier(NON_VERIFIER_UID)) { false }
+ whenever(sendBroadcastForPackages(any()))
+ }
+
+ @Test
+ fun verify() {
+ when (params.type) {
+ Type.INTERNAL -> internal()
+ Type.QUERENT -> approvedQuerent()
+ Type.VERIFIER -> approvedVerifier()
+ Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
+ Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+ }.run { /*exhaust*/ }
+ }
+
+ fun internal() {
+ val context: Context = mockThrowOnUnmocked()
+ val target = params.construct(context)
+
+ INTERNAL_UIDS.forEach { runMethod(target, it) }
+ assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ }
+
+ fun approvedQuerent() {
+ val allowUserSelection = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ whenever(
+ enforcePermission(
+ eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
+ anyInt(), anyInt(), anyString()
+ )
+ ) {
+ if (!allowUserSelection.get()) {
+ throw SecurityException()
+ }
+ }
+ }
+ val target = params.construct(context)
+
+ INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+ verifyNoMoreInteractions(context)
+
+ runMethod(target, VERIFIER_UID)
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+ allowUserSelection.set(true)
+
+ runMethod(target, NON_VERIFIER_UID)
+ }
+
+ fun approvedVerifier() {
+ val shouldThrow = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ whenever(
+ enforcePermission(
+ eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
+ anyInt(), anyInt(), anyString()
+ )
+ ) {
+ if (shouldThrow.get()) {
+ throw SecurityException()
+ }
+ }
+ }
+ val target = params.construct(context)
+
+ INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+ verifyNoMoreInteractions(context)
+
+ runMethod(target, VERIFIER_UID)
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+ shouldThrow.set(true)
+
+ assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
+ assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+ }
+
+ fun approvedUserSelector(verifyCrossUser: Boolean) {
+ val allowUserSelection = AtomicBoolean(true)
+ val allowInteractAcrossUsers = AtomicBoolean(true)
+ val context: Context = mockThrowOnUnmocked {
+ whenever(
+ enforcePermission(
+ eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
+ anyInt(), anyInt(), anyString()
+ )
+ ) {
+ if (!allowUserSelection.get()) {
+ throw SecurityException()
+ }
+ }
+ whenever(
+ enforcePermission(
+ eq(android.Manifest.permission.INTERACT_ACROSS_USERS),
+ anyInt(), anyInt(), anyString()
+ )
+ ) {
+ if (!allowInteractAcrossUsers.get()) {
+ throw SecurityException()
+ }
+ }
+ }
+ val target = params.construct(context)
+
+ fun runEachTestCaseWrapped(
+ callingUserId: Int,
+ targetUserId: Int,
+ block: (testCase: () -> Unit) -> Unit = { it.invoke() }
+ ) {
+ block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) }
+ block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runEachTestCaseWrapped(callingUserId, callingUserId)
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId)
+ }
+
+ allowInteractAcrossUsers.set(false)
+
+ runEachTestCaseWrapped(callingUserId, callingUserId)
+
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ }
+
+ allowUserSelection.set(false)
+
+ runEachTestCaseWrapped(callingUserId, callingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ }
+
+ allowInteractAcrossUsers.set(true)
+
+ runEachTestCaseWrapped(callingUserId, callingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ if (verifyCrossUser) {
+ runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+ assertThrows(SecurityException::class.java, it)
+ }
+ }
+ }
+
+ private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
+ params.runMethod(target, callingUid, callingUserId, userId, proxy)
+ }
+
+ enum class Type {
+ // System/shell only
+ INTERNAL,
+
+ // INTERNAL || domain verification agent || user setting permission holder
+ QUERENT,
+
+ // INTERNAL || domain verification agent
+ VERIFIER,
+
+ // Holding the user setting permission
+ SELECTOR,
+
+ // Holding the user setting permission, but targeting cross user
+ SELECTOR_USER
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
new file mode 100644
index 000000000000..9a3bd994eac0
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.IntentFilterVerificationInfo
+import android.content.pm.PackageManager
+import android.util.ArraySet
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class DomainVerificationLegacySettingsTest {
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun writeAndReadBackNormal() {
+ val settings = DomainVerificationLegacySettings().apply {
+ add(
+ "com.test.one",
+ IntentFilterVerificationInfo(
+ "com.test.one",
+ ArraySet(setOf("example1.com", "example2.com"))
+ ).apply {
+ status = PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK
+ }
+ )
+ add(
+ "com.test.one",
+ 0, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ )
+ add(
+ "com.test.one",
+ 10, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+ )
+
+ add(
+ "com.test.two",
+ IntentFilterVerificationInfo(
+ "com.test.two",
+ ArraySet(setOf("example3.com"))
+ )
+ )
+
+ add(
+ "com.test.three",
+ 11, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ )
+ }
+
+
+ val file = tempFolder.newFile().writeXml(settings::writeSettings)
+ val newSettings = file.readXml {
+ DomainVerificationLegacySettings().apply {
+ readSettings(it)
+ }
+ }
+
+ val xml = file.readText()
+
+ // Legacy migrated settings doesn't bother writing the legacy verification info
+ assertWithMessage(xml).that(newSettings.remove("com.test.one")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 0))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 10))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+ val firstUserStates = newSettings.getUserStates("com.test.one")
+ assertWithMessage(xml).that(firstUserStates).isNotNull()
+ assertWithMessage(xml).that(firstUserStates!!.size()).isEqualTo(2)
+ assertWithMessage(xml).that(firstUserStates[0])
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ assertWithMessage(xml).that(firstUserStates[10])
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+ assertWithMessage(xml).that(newSettings.remove("com.test.two")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserStates("com.test.two")).isNull()
+
+ assertWithMessage(xml).that(newSettings.remove("com.test.three")).isNull()
+ assertWithMessage(xml).that(newSettings.getUserState("com.test.three", 11))
+ .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
new file mode 100644
index 000000000000..a76d8cee582c
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+
+operator fun <F> android.util.Pair<F, *>.component1() = first
+operator fun <S> android.util.Pair<*, S>.component2() = second
+
+operator fun DomainVerificationRequest.component1() = packageNames
+
+operator fun DomainVerificationInfo.component1() = identifier
+operator fun DomainVerificationInfo.component2() = packageName
+operator fun DomainVerificationInfo.component3() = hostToStateMap
+
+operator fun DomainVerificationUserSelection.component1() = identifier
+operator fun DomainVerificationUserSelection.component2() = packageName
+operator fun DomainVerificationUserSelection.component3() = user
+operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap
+
+operator fun DomainVerificationPersistence.ReadResult.component1() = active
+operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
new file mode 100644
index 000000000000..a76152c9df7d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain.DomainVerificationManager
+import android.util.ArrayMap
+import android.util.TypedXmlPullParser
+import android.util.TypedXmlSerializer
+import android.util.Xml
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+
+class DomainVerificationPersistenceTest {
+
+ companion object {
+ private val PKG_PREFIX = DomainVerificationPersistenceTest::class.java.`package`!!.name
+
+ internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply {
+ outputStream().use {
+ // Explicitly use string based XML so it can printed in the test failure output
+ Xml.newFastSerializer()
+ .apply {
+ setOutput(it, StandardCharsets.UTF_8.name())
+ startDocument(null, true)
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ }
+ .apply(block)
+ .endDocument()
+ }
+ }
+
+ internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) =
+ inputStream().use {
+ block(Xml.resolvePullParser(it))
+ }
+ }
+
+ @Rule
+ @JvmField
+ val tempFolder = TemporaryFolder()
+
+ @Test
+ fun writeAndReadBackNormal() {
+ val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
+ mockPkgState(0).let { put(it.packageName, it.id, it) }
+ mockPkgState(1).let { put(it.packageName, it.id, it) }
+ }
+ val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
+ mockPkgState(2).let { put(it.packageName, it) }
+ mockPkgState(3).let { put(it.packageName, it) }
+ }
+ val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
+ mockPkgState(4).let { put(it.packageName, it) }
+ mockPkgState(5).let { put(it.packageName, it) }
+ }
+
+ val file = tempFolder.newFile().writeXml {
+ DomainVerificationPersistence.writeToXml(it, attached, pending, restored)
+ }
+
+ val xml = file.readText()
+
+ val (readActive, readRestored) = file.readXml {
+ DomainVerificationPersistence.readFromXml(it)
+ }
+
+ assertWithMessage(xml).that(readActive.values)
+ .containsExactlyElementsIn(attached.values() + pending.values)
+ assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
+ }
+
+ @Test
+ fun readMalformed() {
+ val stateZero = mockEmptyPkgState(0).apply {
+ stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS
+ stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED
+
+ // A domain without a written state falls back to default
+ stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
+
+ userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ addHosts(setOf("example-user1.com", "example-user1.org"))
+ isDisallowLinkHandling = false
+ }
+ }
+ val stateOne = mockEmptyPkgState(1).apply {
+ // It's valid to have a user selection without any autoVerify domains
+ userSelectionStates[1] = DomainVerificationUserState(1).apply {
+ addHosts(setOf("example-user1.com", "example-user1.org"))
+ isDisallowLinkHandling = true
+ }
+ }
+
+ // Also valid to have neither autoVerify domains nor any active user states
+ val stateTwo = mockEmptyPkgState(2, hasAutoVerifyDomains = false)
+
+ // language=XML
+ val xml = """
+ <?xml?>
+ <domain-verifications>
+ <active>
+ <package-state
+ packageName="${stateZero.packageName}"
+ id="${stateZero.id}"
+ >
+ <state>
+ <domain name="duplicate-takes-last.com" state="1"/>
+ </state>
+ </package-state>
+ <package-state
+ packageName="${stateZero.packageName}"
+ id="${stateZero.id}"
+ hasAutoVerifyDomains="true"
+ >
+ <state>
+ <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
+ <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+ <not-domain name="not-domain.com" state="1"/>
+ <domain name="missing-state.com"/>
+ </state>
+ <user-states>
+ <user-state userId="1" disallowLinkHandling="false">
+ <enabled-hosts>
+ <host name="example-user1.com"/>
+ <not-host name="not-host.com"/>
+ <host/>
+ </enabled-hosts>
+ <enabled-hosts>
+ <host name="example-user1.org"/>
+ </enabled-hosts>
+ <enabled-hosts/>
+ </user-state>
+ <user-state>
+ <enabled-hosts>
+ <host name="no-user-id.com"/>
+ </enabled-hosts>
+ </user-state>
+ </user-states>
+ </package-state>
+ </active>
+ <not-active/>
+ <restored>
+ <package-state
+ packageName="${stateOne.packageName}"
+ id="${stateOne.id}"
+ hasAutoVerifyDomains="true"
+ >
+ <state/>
+ <user-states>
+ <user-state userId="1" disallowLinkHandling="true">
+ <enabled-hosts>
+ <host name="example-user1.com"/>
+ <host name="example-user1.org"/>
+ </enabled-hosts>
+ </user-state>
+ </user-states>
+ </package-state>
+ <package-state packageName="${stateTwo.packageName}"/>
+ <package-state id="${stateTwo.id}"/>
+ <package-state
+ packageName="${stateTwo.packageName}"
+ id="${stateTwo.id}"
+ hasAutoVerifyDomains="false"
+ >
+ <state/>
+ <user-states/>
+ </package-state>
+ </restore>
+ <not-restored/>
+ </domain-verifications>
+ """.trimIndent()
+
+ val (active, restored) = DomainVerificationPersistence
+ .readFromXml(Xml.resolvePullParser(xml.byteInputStream()))
+
+ assertThat(active.values).containsExactly(stateZero)
+ assertThat(restored.values).containsExactly(stateOne, stateTwo)
+ }
+
+ private fun mockEmptyPkgState(
+ id: Int,
+ hasAutoVerifyDomains: Boolean = true
+ ): DomainVerificationPkgState {
+ val pkgName = pkgName(id)
+ val domainSetId = UUID(0L, id.toLong())
+ return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains)
+ }
+
+ private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
+ stateMap["$packageName.com"] = id
+ userSelectionStates[id] = DomainVerificationUserState(id).apply {
+ addHosts(setOf("$packageName-user.com"))
+ isDisallowLinkHandling = true
+ }
+ }
+
+ private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
new file mode 100644
index 000000000000..db541f672954
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
@@ -0,0 +1,500 @@
+/*
+ * 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 android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationState
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.DeviceIdleInternal
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+
+@Suppress("DEPRECATION")
+class DomainVerificationProxyTest {
+
+ companion object {
+ private const val TEST_PKG_NAME_ONE = "com.test.pkg.one"
+ private const val TEST_PKG_NAME_TWO = "com.test.pkg.two"
+ private const val TEST_PKG_NAME_TARGET_ONE = "com.test.target.one"
+ private const val TEST_PKG_NAME_TARGET_TWO = "com.test.target.two"
+ private const val TEST_CALLING_UID_ACCEPT = 40
+ private const val TEST_CALLING_UID_REJECT = 41
+ private val TEST_UUID_ONE = UUID.fromString("f7fbb7dd-7b5f-4609-a95e-c6c7765fb9cd")
+ private val TEST_UUID_TWO = UUID.fromString("4a09b361-a967-43ac-9d18-07a385dff740")
+ }
+
+ private val componentOne = ComponentName(TEST_PKG_NAME_ONE, ".ReceiverOne")
+ private val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverTwo")
+ private val componentThree = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverThree")
+
+ private lateinit var context: Context
+ private lateinit var manager: DomainVerificationManagerInternal
+ private lateinit var collector: DomainVerificationCollector
+
+ // Must be declared as field to support generics
+ @Captor
+ lateinit var hostCaptor: ArgumentCaptor<Set<String>>
+
+ @Before
+ fun setUpMocks() {
+ MockitoAnnotations.initMocks(this)
+ context = mockThrowOnUnmocked {
+ whenever(sendBroadcastAsUser(any(), any(), any(), any<Bundle>()))
+ whenever(
+ enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT),
+ anyString()
+ )
+ )
+ }
+ manager = mockThrowOnUnmocked {
+ whenever(getDomainVerificationInfoId(any())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> TEST_UUID_ONE
+ TEST_PKG_NAME_TARGET_TWO -> TEST_UUID_TWO
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(getDomainVerificationInfo(anyString())) {
+ when (val pkgName = arguments[0] as String) {
+ TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo(
+ TEST_UUID_ONE, pkgName, mapOf(
+ "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ )
+ )
+ TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo(
+ TEST_UUID_TWO, pkgName, mapOf(
+ "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+ "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ )
+ )
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
+ }
+ collector = mockThrowOnUnmocked {
+ whenever(collectAutoVerifyDomains(any())) {
+ when (val pkgName = (arguments[0] as AndroidPackage).packageName) {
+ TEST_PKG_NAME_TARGET_ONE -> ArraySet(setOf("example1.com", "example2.com"))
+ TEST_PKG_NAME_TARGET_TWO -> ArraySet(setOf("example3.com", "example4.com"))
+ else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun isCallerVerifierV1() {
+ val connection = mockConnection()
+ val proxyV1 = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, null, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierV2() {
+ val connection = mockConnection()
+ val proxyV2 = DomainVerificationProxy.makeProxy<Connection>(
+ null, componentTwo, context,
+ manager, collector, connection
+ )
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ }
+
+ @Test
+ fun isCallerVerifierBoth() {
+ val connection = mockConnection()
+ val proxyBoth = DomainVerificationProxy.makeProxy<Connection>(
+ componentTwo, componentThree,
+ context, manager, collector, connection
+ )
+
+ // The combined proxy should only ever call v2 when it succeeds
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+ verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+ verifyNoMoreInteractions(connection)
+ clearInvocations(connection)
+
+ val callingUidCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ // But will call both when v2 fails
+ assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+ verify(connection, times(2))
+ .isCallerPackage(callingUidCaptor.capture(), eq(TEST_PKG_NAME_TWO))
+ verifyNoMoreInteractions(connection)
+
+ assertThat(callingUidCaptor.allValues.toSet()).containsExactly(TEST_CALLING_UID_REJECT)
+ }
+
+ @Test
+ fun differentPackagesResolvesOnlyV2() {
+ assertThat(DomainVerificationProxy.makeProxy<Connection>(
+ componentOne, componentTwo,
+ context, manager, collector, mockConnection()
+ )).isInstanceOf(DomainVerificationProxyV2::class.java)
+ }
+
+ private fun prepareProxyV1(): ProxyV1Setup {
+ val messages = mutableListOf<Pair<Int, Any?>>()
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ componentOne,
+ null,
+ context,
+ manager,
+ collector,
+ connection
+ )
+ return ProxyV1Setup(messages, connection, proxy)
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV1() {
+ val (messages, _, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(2)
+ intents.forEach {
+ assertThat(it.action).isEqualTo(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+ assertThat(it.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME))
+ .isEqualTo(IntentFilter.SCHEME_HTTPS)
+ assertThat(it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1))
+ .isNotEqualTo(-1)
+ }
+
+ intents[0].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_ONE)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example1.com example2.com")
+ }
+
+ intents[1].apply {
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+ .isEqualTo(TEST_PKG_NAME_TARGET_TWO)
+ assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+ .isEqualTo("example3.com example4.com")
+ }
+ }
+
+ private fun prepareProxyOnIntentFilterVerifiedV1(): Pair<ProxyV1Setup, Pair<Int, Int>> {
+ val (messages, connection, proxy) = prepareProxyV1()
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+ messages.clear()
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context, times(2)).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+
+ val verificationIds = intentCaptor.allValues.map {
+ it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1)
+ }
+
+ assertThat(verificationIds).doesNotContain(-1)
+
+ return ProxyV1Setup(messages, connection, proxy) to
+ (verificationIds[0] to verificationIds[1])
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFullSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+ emptyList(),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ assertThat(messages).hasSize(2)
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationManager.STATE_SUCCESS)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedPartialSuccessV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+ val stateCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(4)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ stateCaptor.capture()
+ )
+
+ assertThat(idCaptor.allValues)
+ .containsExactly(TEST_UUID_ONE, TEST_UUID_ONE, TEST_UUID_TWO, TEST_UUID_TWO)
+
+ val hostToStates: Map<Set<*>, Int> = hostCaptor.allValues.zip(stateCaptor.allValues).toMap()
+ assertThat(hostToStates).isEqualTo(mapOf(
+ setOf("example1.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example2.com") to DomainVerificationState.STATE_SUCCESS,
+ setOf("example3.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+ setOf("example4.com") to DomainVerificationState.STATE_SUCCESS,
+ ))
+ }
+
+ @Test
+ fun proxyOnIntentFilterVerifiedFailureV1() {
+ val setup = prepareProxyOnIntentFilterVerifiedV1()
+ val (messages, connection, proxy) = setup.first
+ val (idOne, idTwo) = setup.second
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idOne,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example1.com", "example2.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ DomainVerificationProxyV1.queueLegacyVerifyResult(
+ context,
+ connection,
+ idTwo,
+ PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+ listOf("example3.com", "example4.com"),
+ TEST_CALLING_UID_ACCEPT
+ )
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+ @Suppress("UNCHECKED_CAST")
+ verify(manager, times(2)).setDomainVerificationStatusInternal(
+ eq(TEST_CALLING_UID_ACCEPT),
+ idCaptor.capture(),
+ hostCaptor.capture(),
+ eq(DomainVerificationState.STATE_LEGACY_FAILURE)
+ )
+
+ assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+ assertThat(hostCaptor.allValues.toSet()).containsExactly(
+ setOf("example1.com", "example2.com"),
+ setOf("example3.com", "example4.com")
+ )
+ }
+
+ @Test
+ fun sendBroadcastForPackagesV2() {
+ val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverOne")
+ val messages = mutableListOf<Pair<Int, Any?>>()
+
+ val connection = mockConnection {
+ whenever(schedule(anyInt(), any())) {
+ messages.add((arguments[0] as Int) to arguments[1])
+ }
+ }
+
+ val proxy = DomainVerificationProxy.makeProxy<Connection>(
+ null,
+ componentTwo,
+ context,
+ manager,
+ collector,
+ connection
+ )
+
+ proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+
+ messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+ val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+ verify(context).sendBroadcastAsUser(
+ intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+ )
+ verifyNoMoreInteractions(context)
+
+ val intents = intentCaptor.allValues
+ assertThat(intents).hasSize(1)
+ intents.single().apply {
+ assertThat(this.action).isEqualTo(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+ val request: DomainVerificationRequest? =
+ getParcelableExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST)
+ assertThat(request?.packageNames).containsExactly(
+ TEST_PKG_NAME_TARGET_ONE,
+ TEST_PKG_NAME_TARGET_TWO
+ )
+ }
+ }
+
+ private fun mockConnection(block: Connection.() -> Unit = {}) =
+ mockThrowOnUnmocked<Connection> {
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)) { true }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)) { false }
+ whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)) { false }
+ whenever(getPackage(anyString())) { mockPkg(arguments[0] as String) }
+ whenever(powerSaveTempWhitelistAppDuration) { 1000 }
+ whenever(deviceIdleInternal) {
+ mockThrowOnUnmocked<DeviceIdleInternal> {
+ whenever(
+ addPowerSaveTempWhitelistApp(
+ anyInt(), anyString(), anyLong(), anyInt(),
+ anyBoolean(), anyString()
+ )
+ )
+ }
+ }
+ block()
+ }
+
+ private fun mockPkg(pkgName: String): AndroidPackage {
+ return mockThrowOnUnmocked { whenever(packageName) { pkgName } }
+ }
+
+ private data class ProxyV1Setup(
+ val messages: MutableList<Pair<Int, Any?>>,
+ val connection: Connection,
+ val proxy: DomainVerificationProxy
+ )
+
+ interface Connection : DomainVerificationProxyV1.Connection,
+ DomainVerificationProxyV2.Connection
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c522541b166f..6e27b3a8166c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -64,6 +64,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
@@ -142,7 +144,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable())) {
+ nullable(), nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
@@ -183,6 +185,8 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val dexManager: DexManager = mock()
val installer: Installer = mock()
val displayMetrics: DisplayMetrics = mock()
+ val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
+ val handler = TestHandler(null)
}
companion object {
@@ -258,6 +262,9 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
whenever(mocks.injector.userManagerInternal).thenReturn(mocks.userManagerInternal)
whenever(mocks.injector.installer).thenReturn(mocks.installer)
whenever(mocks.injector.displayMetrics).thenReturn(mocks.displayMetrics)
+ whenever(mocks.injector.domainVerificationManagerInternal)
+ .thenReturn(mocks.domainVerificationManagerInternal)
+ whenever(mocks.injector.handler) { mocks.handler }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 90edaef4294f..709b009c2feb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -37,9 +37,10 @@ public class KeySetManagerServiceTest extends AndroidTestCase {
private KeySetManagerService mKsms;
public PackageSetting generateFakePackageSetting(String name) {
- return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"),
- "", "", "", "", 1, 0, 0, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/);
+ return new PackageSettingBuilder()
+ .setName(name)
+ .setCodePath(new File(mContext.getCacheDir(), "fakeCodePath").getAbsolutePath())
+ .build();
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 4ce1bbc0d017..558fb309ad98 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -107,10 +107,15 @@ public class PackageManagerServiceTest {
// Create a real (non-null) PackageSetting and confirm that the removed
// users are copied properly
- setting = new PackageSetting("name", "realName", new File("codePath"),
- "legacyNativeLibraryPathString", "primaryCpuAbiString", "secondaryCpuAbiString",
- "cpuAbiOverrideString", 0, 0, 0, 0,
- null, null, null);
+ setting = new PackageSettingBuilder()
+ .setName("name")
+ .setRealName("realName")
+ .setCodePath("codePath")
+ .setLegacyNativeLibraryPathString("legacyNativeLibraryPathString")
+ .setPrimaryCpuAbiString("primaryCpuAbiString")
+ .setSecondaryCpuAbiString("secondaryCpuAbiString")
+ .setCpuAbiOverrideString("cpuAbiOverrideString")
+ .build();
pri.populateUsers(new int[] {
1, 2, 3, 4, 5
}, setting);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 333ec9295b93..59458e8df118 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -33,10 +33,10 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -59,6 +59,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
@@ -80,6 +81,7 @@ import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -94,10 +96,14 @@ public class PackageManagerSettingsTests {
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
LegacyPermissionDataProvider mPermissionDataProvider;
+ @Mock
+ DomainVerificationManagerInternal mDomainVerificationManager;
@Before
public void initializeMocks() {
MockitoAnnotations.initMocks(this);
+ when(mDomainVerificationManager.generateNewId())
+ .thenAnswer(invocation -> UUID.randomUUID());
}
@Before
@@ -112,10 +118,7 @@ public class PackageManagerSettingsTests {
throws ReflectiveOperationException, IllegalAccessException {
/* write out files and read */
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
verifyKeySetMetaData(settings);
}
@@ -126,10 +129,7 @@ public class PackageManagerSettingsTests {
throws ReflectiveOperationException, IllegalAccessException {
// write out files and read
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
// write out, read back in and verify the same
@@ -142,10 +142,7 @@ public class PackageManagerSettingsTests {
public void testSettingsReadOld() {
// Write delegateshellthe package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -164,16 +161,12 @@ public class PackageManagerSettingsTests {
public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
settings.writeLPr();
// Create Settings again to make it read from the new files
- settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ settings = makeSettings();
assertThat(settings.readLPw(createFakeUsers()), is(true));
PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -200,10 +193,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadPackageRestrictions_noSuspendingPackage() {
writePackageRestrictions_noSuspendingPackageXml(0);
- final Object lock = new Object();
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- lock);
+ Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendingPackage");
watcher.register();
@@ -244,10 +234,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadPackageRestrictions_noSuspendParamsMap() {
writePackageRestrictions_noSuspendParamsMapXml(0);
- final Object lock = new Object();
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- lock);
+ final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher =
new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
watcher.register();
@@ -281,9 +268,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_suspendInfo() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- new Object());
+ final Settings settingsUnderTest = makeSettings();
final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
watcher.register();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -397,9 +382,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_distractionFlags() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
- new Object());
+ final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
@@ -440,10 +423,7 @@ public class PackageManagerSettingsTests {
@Test
public void testWriteReadUsesStaticLibraries() {
- final Context context = InstrumentationRegistry.getTargetContext();
- final Object lock = new Object();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
ps1.appId = Process.FIRST_APPLICATION_UID;
ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
@@ -516,10 +496,7 @@ public class PackageManagerSettingsTests {
public void testEnableDisable() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- Settings settings = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
watcher.register();
assertThat(settings.readLPw(createFakeUsers()), is(true));
@@ -585,7 +562,8 @@ public class PackageManagerSettingsTests {
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -606,7 +584,8 @@ public class PackageManagerSettingsTests {
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
final PackageSetting testPkgSetting01 = new PackageSetting(
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
@@ -621,7 +600,8 @@ public class PackageManagerSettingsTests {
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
testPkgSetting01.copyFrom(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
}
@@ -648,7 +628,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.pkgFlags, is(0));
@@ -681,7 +662,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -698,12 +680,9 @@ public class PackageManagerSettingsTests {
/** Update package; changing shared user throws exception */
@Test
public void testUpdatePackageSetting03() {
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- final Settings testSettings01 = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
- testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 =
createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
try {
@@ -720,7 +699,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -752,7 +732,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -790,7 +771,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(0));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -808,12 +790,9 @@ public class PackageManagerSettingsTests {
/** Create PackageSetting for a shared user */
@Test
public void testCreateNewSetting03() {
- final Context context = InstrumentationRegistry.getContext();
- final Object lock = new Object();
- final Settings testSettings01 = new Settings(context.getFilesDir(),
- mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+ Settings settings = makeSettings();
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
- testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+ settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 = Settings.createNewSetting(
PACKAGE_NAME,
null /*originalPkg*/,
@@ -834,7 +813,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -875,7 +855,8 @@ public class PackageManagerSettingsTests {
UserManagerService.getInstance(),
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
assertThat(testPkgSetting01.appId, is(10064));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -934,6 +915,7 @@ public class PackageManagerSettingsTests {
assertThat(origPkgSetting.getPathString(), is(testPkgSetting.getPathString()));
assertSame(origPkgSetting.cpuAbiOverrideString, testPkgSetting.cpuAbiOverrideString);
assertThat(origPkgSetting.cpuAbiOverrideString, is(testPkgSetting.cpuAbiOverrideString));
+ assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId()));
assertThat(origPkgSetting.firstInstallTime, is(testPkgSetting.firstInstallTime));
assertSame(origPkgSetting.installSource, testPkgSetting.installSource);
assertThat(origPkgSetting.installPermissionsFixed,
@@ -976,8 +958,6 @@ public class PackageManagerSettingsTests {
assertNotSame(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
// No equals() method for SparseArray object
// assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
- assertSame(origPkgSetting.verificationInfo, testPkgSetting.verificationInfo);
- assertThat(origPkgSetting.verificationInfo, is(testPkgSetting.verificationInfo));
assertThat(origPkgSetting.versionCode, is(testPkgSetting.versionCode));
assertSame(origPkgSetting.volumeUuid, testPkgSetting.volumeUuid);
assertThat(origPkgSetting.volumeUuid, is(testPkgSetting.volumeUuid));
@@ -1006,7 +986,8 @@ public class PackageManagerSettingsTests {
sharedUserId,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
}
private PackageSetting createPackageSetting(String packageName) {
@@ -1024,7 +1005,8 @@ public class PackageManagerSettingsTests {
0,
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/);
+ null /*mimeGroups*/,
+ UUID.randomUUID());
}
private @NonNull List<UserInfo> createFakeUsers() {
@@ -1212,6 +1194,12 @@ public class PackageManagerSettingsTests {
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
+ private Settings makeSettings() {
+ return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
+ mRuntimePermissionsPersistence, mPermissionDataProvider,
+ mDomainVerificationManager, new Object());
+ }
+
private void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
ArrayMap<String, PackageSetting> packages =
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 90c29824409f..e6a238a775c6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -546,12 +546,17 @@ public class PackageParserTest {
}
private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
- return new PackageSetting(pkg.getPackageName(), pkg.getRealPackage(),
- new File(pkg.getPath()), null, pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi(),
- null, pkg.getVersionCode(),
- PackageInfoUtils.appInfoFlags(pkg, null),
- PackageInfoUtils.appInfoPrivateFlags(pkg, null),
- pkg.getSharedUserLabel(), null, null, null);
+ return new PackageSettingBuilder()
+ .setName(pkg.getPackageName())
+ .setRealName(pkg.getRealPackage())
+ .setCodePath(pkg.getPath())
+ .setPrimaryCpuAbiString(pkg.getPrimaryCpuAbi())
+ .setSecondaryCpuAbiString(pkg.getSecondaryCpuAbi())
+ .setPVersionCode(pkg.getLongVersionCode())
+ .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
+ .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
+ .setSharedUserId(pkg.getSharedUserLabel())
+ .build();
}
// NOTE: The equality assertions below are based on code autogenerated by IntelliJ.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 84551c51052c..f75751bf54ae 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,10 +22,10 @@ import android.util.ArraySet;
import android.util.SparseArray;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
import java.io.File;
import java.util.Map;
+import java.util.UUID;
public class PackageSettingBuilder {
private String mName;
@@ -48,6 +48,7 @@ public class PackageSettingBuilder {
private long[] mUsesStaticLibrariesVersions;
private Map<String, ArraySet<String>> mMimeGroups;
private PackageParser.SigningDetails mSigningDetails;
+ private UUID mDomainSetId = UUID.randomUUID();
public PackageSettingBuilder setPackage(AndroidPackage pkg) {
this.mPkg = pkg;
@@ -163,12 +164,17 @@ public class PackageSettingBuilder {
return this;
}
+ public PackageSettingBuilder setDomainSetId(UUID domainSetId) {
+ mDomainSetId = domainSetId;
+ return this;
+ }
+
public PackageSetting build() {
final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions,
- mMimeGroups);
+ mMimeGroups, mDomainSetId);
packageSetting.signatures = mSigningDetails != null
? new PackageSignatures(mSigningDetails)
: new PackageSignatures();
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 90658055ad6f..27f3eec655e3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -465,10 +465,11 @@ public class PackageSignaturesTest {
private static PackageSetting createPackageSetting() {
// Generic PackageSetting object with values from a test app installed on a device to be
// used to test the methods under the PackageSignatures signatures data member.
- File appPath = new File("/data/app/app");
- PackageSetting result = new PackageSetting("test.app", null, appPath,
- "/data/app/app", null, null, null, 1, 940097092, 0, 0 /*userId*/, null, null,
- null /*mimeGroups*/);
- return result;
+ return new PackageSettingBuilder()
+ .setName("test.app")
+ .setCodePath("/data/app/app")
+ .setPVersionCode(1)
+ .setPkgFlags(940097092)
+ .build();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 1cfbad93c2e5..938e4cc84e62 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -53,18 +53,10 @@ public class PackageUserStateTest {
assertThat(testUserState.equals(oldUserState), is(true));
oldUserState = new PackageUserState();
- oldUserState.appLinkGeneration = 6;
- assertThat(testUserState.equals(oldUserState), is(false));
-
- oldUserState = new PackageUserState();
oldUserState.ceDataInode = 4000L;
assertThat(testUserState.equals(oldUserState), is(false));
oldUserState = new PackageUserState();
- oldUserState.domainVerificationStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
- assertThat(testUserState.equals(oldUserState), is(false));
-
- oldUserState = new PackageUserState();
oldUserState.enabled = COMPONENT_ENABLED_STATE_ENABLED;
assertThat(testUserState.equals(oldUserState), is(false));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index d8c3979c9cf9..b5add849c2dc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -50,6 +50,7 @@ import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.PackageImpl;
@@ -91,6 +92,14 @@ public class ScanTests {
when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper);
when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager);
when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility);
+
+ DomainVerificationManagerInternal domainVerificationManager =
+ mock(DomainVerificationManagerInternal.class);
+ when(domainVerificationManager.generateNewId())
+ .thenAnswer(invocation -> UUID.randomUUID());
+
+ when(mMockInjector.getDomainVerificationManagerInternal())
+ .thenReturn(domainVerificationManager);
}
@Before
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index 4c82818f71e4..c6e35cf84355 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -26,21 +26,17 @@ import org.mockito.stubbing.Stubber
object MockitoUtils {
val ANSWER_THROWS = Answer<Any?> {
when (val name = it.method.name) {
- "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ "toString" -> return@Answer try {
+ Answers.CALLS_REAL_METHODS.answer(it)
+ } catch (e: Exception) {
+ "failure calling toString"
+ }
else -> {
val arguments = it.arguments
?.takeUnless { it.isEmpty() }
- ?.mapIndexed { index, arg ->
- try {
- arg?.toString()
- } catch (e: Exception) {
- "toString[$index] threw ${e.message}"
- }
- }
- ?.joinToString()
- ?.let {
- "with $it"
- }
+ ?.mapIndexed { index, arg -> arg.attemptToString(index) }
+ ?.joinToString { it.attemptToString(null) }
+ ?.let { "with $it" }
.orEmpty()
throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
@@ -48,6 +44,19 @@ object MockitoUtils {
}
}
}
+
+ // Sometimes mocks won't have a toString method, so try-catch and return some default
+ private fun Any?.attemptToString(id: Any? = null): String {
+ return try {
+ toString()
+ } catch (e: Exception) {
+ if (id == null) {
+ e.message ?: "ERROR"
+ } else {
+ "$id ${e.message}"
+ }
+ }
+ }
}
inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
@@ -83,4 +92,4 @@ inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit = {}):
inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) =
spyThrowOnUnmocked<T>(null, block)
-inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java) \ No newline at end of file
+inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)