diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8cae855455127..1c19918f7add5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2103,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"; @@ -2146,6 +2147,7 @@ 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"; @@ -2701,6 +2703,31 @@ package android.content.pm.dex { package android.content.pm.domain.verify { + public interface DomainVerificationManager { + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}, conditional=true) public android.content.pm.domain.verify.DomainVerificationSet getDomainVerificationSet(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.domain.verify.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.QUERY_ALL_PACKAGES}) public java.util.List 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(allOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.QUERY_ALL_PACKAGES}) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set, int) throws android.content.pm.domain.verify.DomainVerificationManager.InvalidDomainSetException, 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, boolean) throws android.content.pm.domain.verify.DomainVerificationManager.InvalidDomainSetException, android.content.pm.PackageManager.NameNotFoundException; + field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.domain.verify.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 static class DomainVerificationManager.InvalidDomainSetException extends android.util.AndroidException { + method @Nullable public java.util.UUID getDomainSetId(); + method @Nullable public String getPackageName(); + method public int getReason(); + field public static final int REASON_ID_INVALID = 2; // 0x2 + field public static final int REASON_ID_NULL = 1; // 0x1 + field public static final int REASON_SET_NULL_OR_EMPTY = 3; // 0x3 + field public static final int REASON_UNKNOWN_DOMAIN = 4; // 0x4 + } + public final class DomainVerificationRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Set getPackageNames(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4dc41b2d51144..987de3fca6b1b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 1752b480c06b5..e6753427b908d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2846,6 +2846,21 @@ public class Intent implements Parcelable, Cloneable { @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. + *

+ * This is a protected intent that can only be sent by the system. + *

+ * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DOMAINS_NEED_VERIFICATION = + "android.intent.action.DOMAINS_NEED_VERIFICATION"; + /** * Broadcast Action: Resources for a set of packages (which were * previously unavailable) are currently diff --git a/core/java/android/content/pm/domain/verify/DomainVerificationManager.java b/core/java/android/content/pm/domain/verify/DomainVerificationManager.java new file mode 100644 index 0000000000000..91dcdd562a057 --- /dev/null +++ b/core/java/android/content/pm/domain/verify/DomainVerificationManager.java @@ -0,0 +1,363 @@ +/* + * 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.domain.verify; + +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 android.util.AndroidException; + +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 #getDomainVerificationSet(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.domain.verify.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"; + 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: + 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: + 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: + default: + return false; + } + } + + /** + * Used to iterate all {@link DomainVerificationSet} 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(allOf = { + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, + android.Manifest.permission.QUERY_ALL_PACKAGES + }) + List getValidVerificationPackageNames(); + + /** + * Retrieves the domain verification state for a given package. The caller must be the domain + * verification agent for the device with + * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}, or hold + * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}. + * Also requires that the caller have the + * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission in addition to either of + * the requirements above. + * + * @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(allOf = { + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, + android.Manifest.permission.QUERY_ALL_PACKAGES, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + }, conditional = true) + DomainVerificationSet getDomainVerificationSet(@NonNull String packageName) + throws NameNotFoundException; + + /** + * Change the verification status of the {@param domains} of the package associated with + * {@param domainSetId}. + * + * @param domainSetId See {@link DomainVerificationSet#getIdentifier()}. + * @param domains List of host names to change the state of. + * @param state See {@link DomainVerificationSet#getHostToStateMap()}. + * @throws InvalidDomainSetException 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(allOf = { + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, + android.Manifest.permission.QUERY_ALL_PACKAGES + }) + void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set domains, + @DomainVerificationState.State int state) + throws InvalidDomainSetException, 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 DomainVerificationSet#getIdentifier()}. + * @param domains The domains to toggle the state of. + * @param enabled Whether or not the app should automatically open the domains specified. + * @throws InvalidDomainSetException 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 domains, boolean enabled) + throws InvalidDomainSetException, 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 DomainVerificationSet#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 #getDomainVerificationSet(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. + */ + class InvalidDomainSetException extends AndroidException { + + 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 { + } + + private static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName, + @Reason int reason) { + switch (reason) { + case REASON_ID_NULL: + return "Domain set ID cannot be null"; + case REASON_ID_INVALID: + return "Domain set ID " + domainSetId + " has been invalidated"; + case REASON_SET_NULL_OR_EMPTY: + return "Domain set cannot be null or empty"; + case REASON_UNKNOWN_DOMAIN: + return "Domain set contains value that was not declared by the target package " + + packageName; + default: + return "Unknown failure"; + } + } + + @Reason + private final int mReason; + + @Nullable + private final UUID mDomainSetId; + + @Nullable + private final String mPackageName; + + /** @hide */ + public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, + @Reason int reason) { + super(buildMessage(domainSetId, packageName, reason)); + mDomainSetId = domainSetId; + mPackageName = packageName; + mReason = reason; + } + + @Nullable + public UUID getDomainSetId() { + return mDomainSetId; + } + + @Nullable + public String getPackageName() { + return mPackageName; + } + + @Reason + public int getReason() { + return mReason; + } + } +} diff --git a/core/java/android/content/pm/domain/verify/DomainVerificationManagerImpl.java b/core/java/android/content/pm/domain/verify/DomainVerificationManagerImpl.java new file mode 100644 index 0000000000000..8ce0e9a79da99 --- /dev/null +++ b/core/java/android/content/pm/domain/verify/DomainVerificationManagerImpl.java @@ -0,0 +1,193 @@ +/* + * 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.domain.verify; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +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 getValidVerificationPackageNames() { + try { + return mDomainVerificationManager.getValidVerificationPackageNames(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + @Override + public DomainVerificationSet getDomainVerificationSet(@NonNull String packageName) + throws NameNotFoundException { + try { + return mDomainVerificationManager.getDomainVerificationSet(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 domains, + int state) throws InvalidDomainSetException, 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 domains, boolean enabled) + throws InvalidDomainSetException, 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 InvalidDomainSetException(domainSetId, packageName, + errorSpecificCode); + case ERROR_NAME_NOT_FOUND: + return new NameNotFoundException(packageName); + default: + return exception; + } + } else if (exception instanceof RemoteException) { + return ((RemoteException) exception).rethrowFromSystemServer(); + } else { + return exception; + } + } +} diff --git a/core/java/android/content/pm/domain/verify/DomainVerificationState.java b/core/java/android/content/pm/domain/verify/DomainVerificationState.java new file mode 100644 index 0000000000000..a9adab91056eb --- /dev/null +++ b/core/java/android/content/pm/domain/verify/DomainVerificationState.java @@ -0,0 +1,93 @@ +/* + * 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.domain.verify; + +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_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. + *

+ * TODO: This currently combines SysConfig and instant app. Is it worth separating those? + */ + 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; + + /** + * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED + */ + int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000; +} diff --git a/core/java/android/content/pm/domain/verify/IDomainVerificationManager.aidl b/core/java/android/content/pm/domain/verify/IDomainVerificationManager.aidl new file mode 100644 index 0000000000000..3726480bec0d4 --- /dev/null +++ b/core/java/android/content/pm/domain/verify/IDomainVerificationManager.aidl @@ -0,0 +1,44 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.domain.verify; + +import android.content.pm.domain.verify.DomainVerificationSet; +import android.content.pm.domain.verify.DomainVerificationUserSelection; +import java.util.List; + +/** + * @see DomainVerificationManager + * @hide + */ +interface IDomainVerificationManager { + + List getValidVerificationPackageNames(); + + @nullable + DomainVerificationSet getDomainVerificationSet(String packageName); + + @nullable + DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, + int userId); + + void setDomainVerificationStatus(String domainSetId, in List domains, int state); + + void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); + + void setDomainVerificationUserSelection(String domainSetId, in List domains, + boolean enabled, int userId); +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6a9d7861cd987..3bff755c63c63 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -93,6 +93,7 @@ +