Merge changes from topic "domain-verify-sc-dev" into sc-dev
* changes: Address domain verification API comments Remove legacy IntentFilterVerificationManager Convert DomainVerificationService.Connection to synchronous set Migrate legacy intent filter verification settings Combine v1 and v2 DomainVerficationProxies Add DomainVerificationProxyV1 Use new always open during PMS Activity resolution Add DomainVerificationShell and boot broadcast Add DomainVerificationDebug Implement domain verification core system APIs Add DomainVerificationEnforcer Add DomainVerificationProxyV2 for new APIs Attach domain verification states during package scan/install Add DomainVerificationSettings write/read to Settings Wire up DomainVerificationService to PackageManagerService Add DomainVerificationCollector Add DomainVerificationPersistence Add DomainVerificationService skeleton Add DomainVerificationManager Add new domain verification permissions Add internal domain verification data classes Add domain verification API classes Refactor legacy domain verification code
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5450,6 +5450,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
12
core/java/android/content/pm/verify/domain/TEST_MAPPING
Normal file
12
core/java/android/content/pm/verify/domain/TEST_MAPPING
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "PackageManagerServiceUnitTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.pm.test.verify.domain"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 , "");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
404
services/core/java/com/android/server/pm/SettingsXml.java
Normal file
404
services/core/java/com/android/server/pm/SettingsXml.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "PackageManagerServiceUnitTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.pm.test.verify.domain"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
29
services/tests/PackageManagerServiceTests/unit/Android.bp
Normal file
29
services/tests/PackageManagerServiceTests/unit/Android.bp
Normal file
@@ -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"],
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,18 +52,10 @@ public class PackageUserStateTest {
|
||||
assertThat(testUserState.equals(testUserState), is(true));
|
||||
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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
|
||||
|
||||
Reference in New Issue
Block a user