diff options
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) |