1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content.pm.verify.domain; 18 19 import android.annotation.CheckResult; 20 import android.annotation.FlaggedApi; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.annotation.SystemService; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.UriRelativeFilterGroup; 30 import android.content.UriRelativeFilterGroupParcel; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.os.Bundle; 33 import android.os.RemoteException; 34 import android.os.ServiceSpecificException; 35 import android.os.UserHandle; 36 import android.util.ArrayMap; 37 38 import com.android.internal.util.CollectionUtils; 39 40 import java.util.Collections; 41 import java.util.Comparator; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Objects; 45 import java.util.Set; 46 import java.util.SortedSet; 47 import java.util.TreeSet; 48 import java.util.UUID; 49 50 /** 51 * System service to access domain verification APIs. 52 * 53 * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to 54 * check if/how they are verified for a domain, which is required starting from platform 55 * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare 56 * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against 57 * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an 58 * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of 59 * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the 60 * caller when using {@link Context#startActivity(Intent)} and similar. 61 */ 62 @SystemService(Context.DOMAIN_VERIFICATION_SERVICE) 63 public final class DomainVerificationManager { 64 65 /** 66 * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed 67 * to an the domain verification agent that handles 68 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}. 69 * 70 * @hide 71 */ 72 @SystemApi 73 public static final String EXTRA_VERIFICATION_REQUEST = 74 "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; 75 76 /** 77 * Default return code for when a method has succeeded. 78 * 79 * @hide 80 */ 81 @SystemApi 82 public static final int STATUS_OK = 0; 83 84 /** 85 * The provided domain set ID was invalid, probably due to the package being updated between 86 * the initial request that provided the ID and the method call that used it. This usually 87 * means the work being processed by the verification agent is outdated and a new request 88 * should be scheduled, which should already be in progress as part of the 89 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. 90 * 91 * @hide 92 */ 93 @SystemApi 94 public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; 95 96 /** 97 * The provided set of domains contains a domain not declared by the target package. This 98 * usually means the work being processed by the verification agent is outdated and a new 99 * request should be scheduled, which should already be in progress as part of the 100 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. 101 * 102 * @hide 103 */ 104 @SystemApi 105 public static final int ERROR_UNKNOWN_DOMAIN = 2; 106 107 /** 108 * The system was unable to select the domain for approval. This indicates another application 109 * has been granted a higher approval, usually through domain verification, and the target 110 * package is unable to override it. 111 * 112 * @hide 113 */ 114 @SystemApi 115 public static final int ERROR_UNABLE_TO_APPROVE = 3; 116 117 /** 118 * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API. 119 * 120 * @hide 121 */ 122 public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1; 123 124 /** 125 * @hide 126 */ 127 @IntDef(prefix = {"ERROR_"}, value = { 128 ERROR_DOMAIN_SET_ID_INVALID, 129 ERROR_UNKNOWN_DOMAIN, 130 ERROR_UNABLE_TO_APPROVE, 131 }) 132 public @interface Error { 133 } 134 135 private final Context mContext; 136 137 private final IDomainVerificationManager mDomainVerificationManager; 138 139 /** 140 * System service to access the domain verification APIs. 141 * <p> 142 * Allows the approved domain verification agent on the device (the sole holder of {@link 143 * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of 144 * domains declared by applications in their AndroidManifest.xml, to allow them to open those 145 * links inside the app when selected by the user. This is done through querying {@link 146 * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID, 147 * Set, int)}. 148 * <p> 149 * Also allows the domain preference settings (holder of 150 * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) 151 * to update the preferences of the user, when they have chosen to explicitly allow an 152 * application to open links. This is done through querying 153 * {@link #getDomainVerificationUserState(String)} and calling 154 * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and 155 * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. 156 * 157 * @hide 158 */ DomainVerificationManager(Context context, IDomainVerificationManager domainVerificationManager)159 public DomainVerificationManager(Context context, 160 IDomainVerificationManager domainVerificationManager) { 161 mContext = context; 162 mDomainVerificationManager = domainVerificationManager; 163 } 164 165 /** 166 * Update the URI relative filter groups for a package. The groups set using this API acts 167 * as an additional filtering layer during intent resolution. It does not replace any 168 * existing groups that have been added to the package's intent filters either using the 169 * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)} 170 * API or defined in the manifest. 171 * <p> 172 * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the 173 * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups 174 * that are indexed to the specific subdomain of the intent's data URI followed by any groups 175 * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority 176 * will decrease corresponding to the decreasing number of subdomain labels after the wildcard. 177 * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d". 178 * <p> 179 * All previously existing groups set for a domain index using this API will be cleared when 180 * new groups are set. 181 * 182 * @param packageName The name of the package. 183 * @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that 184 * should apply to them. Groups for each domain will replace any groups 185 * provided for that domain in a prior call to this method. To clear 186 * existing groups, set the list to null or a empty list. Groups will 187 * be evaluated in the order they are provided. 188 * 189 * @see UriRelativeFilterGroup 190 * @see android.content.IntentFilter 191 * @hide 192 */ 193 @SystemApi 194 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) 195 @FlaggedApi(android.content.pm.Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) setUriRelativeFilterGroups(@onNull String packageName, @NonNull Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap)196 public void setUriRelativeFilterGroups(@NonNull String packageName, 197 @NonNull Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap) { 198 Objects.requireNonNull(packageName); 199 Objects.requireNonNull(domainToGroupsMap); 200 Bundle bundle = new Bundle(); 201 for (String domain : domainToGroupsMap.keySet()) { 202 List<UriRelativeFilterGroup> groups = domainToGroupsMap.get(domain); 203 bundle.putParcelableList(domain, UriRelativeFilterGroup.groupsToParcels(groups)); 204 } 205 try { 206 mDomainVerificationManager.setUriRelativeFilterGroups(packageName, bundle); 207 } catch (RemoteException e) { 208 throw e.rethrowFromSystemServer(); 209 } 210 } 211 212 /** 213 * Retrieves a map of a package's verified domains to a list of {@link UriRelativeFilterGroup}s 214 * that applies to them. 215 * 216 * @param packageName The name of the package. 217 * @param domains List of domains for which to retrieve group matches. 218 * @return A map of domains to the lists of {@link UriRelativeFilterGroup}s that apply to them. 219 * @hide 220 */ 221 @NonNull 222 @SystemApi 223 @FlaggedApi(android.content.pm.Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS) getUriRelativeFilterGroups( @onNull String packageName, @NonNull List<String> domains)224 public Map<String, List<UriRelativeFilterGroup>> getUriRelativeFilterGroups( 225 @NonNull String packageName, 226 @NonNull List<String> domains) { 227 Objects.requireNonNull(packageName); 228 Objects.requireNonNull(domains); 229 if (domains.isEmpty()) { 230 return Collections.emptyMap(); 231 } 232 try { 233 Bundle bundle = mDomainVerificationManager.getUriRelativeFilterGroups(packageName, 234 domains); 235 ArrayMap<String, List<UriRelativeFilterGroup>> map = new ArrayMap<>(); 236 if (!bundle.isEmpty()) { 237 for (String domain : bundle.keySet()) { 238 List<UriRelativeFilterGroupParcel> parcels = 239 bundle.getParcelableArrayList(domain, 240 UriRelativeFilterGroupParcel.class); 241 map.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels)); 242 } 243 } 244 return map; 245 } catch (RemoteException e) { 246 throw e.rethrowFromSystemServer(); 247 } 248 } 249 250 /** 251 * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is 252 * usually a heavy workload and should be done infrequently. 253 * 254 * @return the current snapshot of package names with valid autoVerify URLs. 255 * @hide 256 */ 257 @SystemApi 258 @NonNull 259 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) queryValidVerificationPackageNames()260 public List<String> queryValidVerificationPackageNames() { 261 try { 262 return mDomainVerificationManager.queryValidVerificationPackageNames(); 263 } catch (RemoteException e) { 264 throw e.rethrowFromSystemServer(); 265 } 266 } 267 268 /** 269 * Retrieves the domain verification state for a given package. 270 * 271 * @return the data for the package, or null if it does not declare any autoVerify domains 272 * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error 273 * and should not be re-tried except on a time scheduled basis. 274 * @hide 275 */ 276 @SystemApi 277 @Nullable 278 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) getDomainVerificationInfo(@onNull String packageName)279 public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) 280 throws NameNotFoundException { 281 try { 282 return mDomainVerificationManager.getDomainVerificationInfo(packageName); 283 } catch (Exception e) { 284 Exception converted = rethrow(e, packageName); 285 if (converted instanceof NameNotFoundException) { 286 throw (NameNotFoundException) converted; 287 } else if (converted instanceof RuntimeException) { 288 throw (RuntimeException) converted; 289 } else { 290 throw new RuntimeException(converted); 291 } 292 } 293 } 294 295 /** 296 * Change the verification status of the {@param domains} of the package associated with {@param 297 * domainSetId}. 298 * 299 * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. 300 * @param domains List of host names to change the state of. 301 * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. 302 * @return error code or {@link #STATUS_OK} if successful 303 * @throws NameNotFoundException If the ID is known to be good, but the package is 304 * unavailable. This may be because the package is installed on 305 * a volume that is no longer mounted. This error is 306 * unrecoverable until the package is available again, and 307 * should not be re-tried except on a time scheduled basis. 308 * @hide 309 */ 310 @CheckResult 311 @SystemApi 312 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) setDomainVerificationStatus(@onNull UUID domainSetId, @NonNull Set<String> domains, int state)313 public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, 314 int state) throws NameNotFoundException { 315 validateInput(domainSetId, domains); 316 317 try { 318 return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), 319 new DomainSet(domains), state); 320 } catch (Exception e) { 321 Exception converted = rethrow(e, null); 322 if (converted instanceof NameNotFoundException) { 323 throw (NameNotFoundException) converted; 324 } else if (converted instanceof RuntimeException) { 325 throw (RuntimeException) converted; 326 } else { 327 throw new RuntimeException(converted); 328 } 329 } 330 } 331 332 /** 333 * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web 334 * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with 335 * the verification status for the specific domain being opened and other system state. An app 336 * with this enabled is not guaranteed to be the sole link handler for its domains. 337 * <p> 338 * By default, all apps are allowed to open links. Users must disable them explicitly. 339 * 340 * @hide 341 */ 342 @SystemApi 343 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) setDomainVerificationLinkHandlingAllowed(@onNull String packageName, boolean allowed)344 public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, 345 boolean allowed) throws NameNotFoundException { 346 try { 347 mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, 348 allowed, mContext.getUserId()); 349 } catch (Exception e) { 350 Exception converted = rethrow(e, null); 351 if (converted instanceof NameNotFoundException) { 352 throw (NameNotFoundException) converted; 353 } else if (converted instanceof RuntimeException) { 354 throw (RuntimeException) converted; 355 } else { 356 throw new RuntimeException(converted); 357 } 358 } 359 } 360 361 /** 362 * Update the recorded user selection for the given {@param domains} for the given {@param 363 * domainSetId}. This state is recorded for the lifetime of a domain for a package on device, 364 * and will never be reset by the system short of an app data clear. 365 * <p> 366 * This state is stored per device user. If another user needs to be changed, the appropriate 367 * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should 368 * be used. 369 * <p> 370 * Enabling an unverified domain will allow an application to open it, but this can only occur 371 * if no other app on the device is approved for a higher approval level. This can queried 372 * using {@link #getOwnersForDomain(String)}. 373 * 374 * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to 375 * enable that domain will disable all other owners. 376 * 377 * On the other hand, if any of the owners are non-overrideable, then this must be called with 378 * false for all of the other owners to disable them before the domain can be taken by a new 379 * owner. 380 * 381 * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. 382 * @param domains The domains to toggle the state of. 383 * @param enabled Whether or not the app should automatically open the domains specified. 384 * @return error code or {@link #STATUS_OK} if successful 385 * @throws NameNotFoundException If the ID is known to be good, but the package is 386 * unavailable. This may be because the package is installed on 387 * a volume that is no longer mounted. This error is 388 * unrecoverable until the package is available again, and 389 * should not be re-tried except on a time scheduled basis. 390 * @hide 391 */ 392 @CheckResult 393 @SystemApi 394 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) setDomainVerificationUserSelection(@onNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled)395 public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, 396 @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { 397 validateInput(domainSetId, domains); 398 399 try { 400 return mDomainVerificationManager.setDomainVerificationUserSelection( 401 domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId()); 402 } catch (Exception e) { 403 Exception converted = rethrow(e, null); 404 if (converted instanceof NameNotFoundException) { 405 throw (NameNotFoundException) converted; 406 } else if (converted instanceof RuntimeException) { 407 throw (RuntimeException) converted; 408 } else { 409 throw new RuntimeException(converted); 410 } 411 } 412 } 413 414 /** 415 * Retrieve the user state for the given package and the {@link Context}'s user. 416 * 417 * @param packageName The app to query state for. 418 * @return The user selection verification data for the given package for the user, or null if 419 * the package does not declare any HTTP/HTTPS domains. 420 */ 421 @Nullable getDomainVerificationUserState( @onNull String packageName)422 public DomainVerificationUserState getDomainVerificationUserState( 423 @NonNull String packageName) throws NameNotFoundException { 424 try { 425 return mDomainVerificationManager.getDomainVerificationUserState(packageName, 426 mContext.getUserId()); 427 } catch (Exception e) { 428 Exception converted = rethrow(e, packageName); 429 if (converted instanceof NameNotFoundException) { 430 throw (NameNotFoundException) converted; 431 } else if (converted instanceof RuntimeException) { 432 throw (RuntimeException) converted; 433 } else { 434 throw new RuntimeException(converted); 435 } 436 } 437 } 438 439 /** 440 * For the given domain, return all apps which are approved to open it in a 441 * greater than 0 priority. This does not mean that all apps can actually open 442 * an Intent with that domain. That will be decided by the set of apps which 443 * are the highest priority level, ignoring all lower priority levels. 444 * 445 * The set will be ordered from lowest to highest priority. 446 * 447 * @param domain The host to query for. An invalid domain will result in an empty set. 448 * 449 * @hide 450 */ 451 @SystemApi 452 @NonNull 453 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) getOwnersForDomain(@onNull String domain)454 public SortedSet<DomainOwner> getOwnersForDomain(@NonNull String domain) { 455 try { 456 Objects.requireNonNull(domain); 457 final List<DomainOwner> orderedList = mDomainVerificationManager.getOwnersForDomain( 458 domain, mContext.getUserId()); 459 SortedSet<DomainOwner> set = new TreeSet<>( 460 Comparator.comparingInt(orderedList::indexOf)); 461 set.addAll(orderedList); 462 return set; 463 } catch (RemoteException e) { 464 throw e.rethrowFromSystemServer(); 465 } 466 } 467 rethrow(Exception exception, @Nullable String packageName)468 private Exception rethrow(Exception exception, @Nullable String packageName) { 469 if (exception instanceof ServiceSpecificException) { 470 int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode; 471 if (packageName == null) { 472 packageName = exception.getMessage(); 473 } 474 475 if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) { 476 return new NameNotFoundException(packageName); 477 } 478 479 return exception; 480 } else if (exception instanceof RemoteException) { 481 return ((RemoteException) exception).rethrowFromSystemServer(); 482 } else { 483 return exception; 484 } 485 } 486 validateInput(@ullable UUID domainSetId, @Nullable Set<String> domains)487 private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) { 488 if (domainSetId == null) { 489 throw new IllegalArgumentException("domainSetId cannot be null"); 490 } else if (CollectionUtils.isEmpty(domains)) { 491 throw new IllegalArgumentException("Provided domain set cannot be empty"); 492 } 493 } 494 } 495