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.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.annotation.SystemService; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.os.RemoteException; 30 import android.os.ServiceSpecificException; 31 import android.os.UserHandle; 32 33 import com.android.internal.util.CollectionUtils; 34 35 import java.util.Comparator; 36 import java.util.List; 37 import java.util.Objects; 38 import java.util.Set; 39 import java.util.SortedSet; 40 import java.util.TreeSet; 41 import java.util.UUID; 42 43 /** 44 * System service to access domain verification APIs. 45 * 46 * Applications should use {@link #getDomainVerificationUserState(String)} if necessary to 47 * check if/how they are verified for a domain, which is required starting from platform 48 * {@link android.os.Build.VERSION_CODES#S} in order to open {@link Intent}s which declare 49 * {@link Intent#CATEGORY_BROWSABLE} or no category and also match against 50 * {@link Intent#CATEGORY_DEFAULT} {@link android.content.IntentFilter}s, either through an 51 * explicit declaration of {@link Intent#CATEGORY_DEFAULT} or through the use of 52 * {@link android.content.pm.PackageManager#MATCH_DEFAULT_ONLY}, which is usually added for the 53 * caller when using {@link Context#startActivity(Intent)} and similar. 54 */ 55 @SystemService(Context.DOMAIN_VERIFICATION_SERVICE) 56 public final class DomainVerificationManager { 57 58 /** 59 * Extra field name for a {@link DomainVerificationRequest} for the requested packages. Passed 60 * to an the domain verification agent that handles 61 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}. 62 * 63 * @hide 64 */ 65 @SystemApi 66 public static final String EXTRA_VERIFICATION_REQUEST = 67 "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; 68 69 /** 70 * Default return code for when a method has succeeded. 71 * 72 * @hide 73 */ 74 @SystemApi 75 public static final int STATUS_OK = 0; 76 77 /** 78 * The provided domain set ID was invalid, probably due to the package being updated between 79 * the initial request that provided the ID and the method call that used it. This usually 80 * means the work being processed by the verification agent is outdated and a new request 81 * should be scheduled, which should already be in progress as part of the 82 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. 83 * 84 * @hide 85 */ 86 @SystemApi 87 public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; 88 89 /** 90 * The provided set of domains contains a domain not declared by the target package. This 91 * usually means the work being processed by the verification agent is outdated and a new 92 * request should be scheduled, which should already be in progress as part of the 93 * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. 94 * 95 * @hide 96 */ 97 @SystemApi 98 public static final int ERROR_UNKNOWN_DOMAIN = 2; 99 100 /** 101 * The system was unable to select the domain for approval. This indicates another application 102 * has been granted a higher approval, usually through domain verification, and the target 103 * package is unable to override it. 104 * 105 * @hide 106 */ 107 @SystemApi 108 public static final int ERROR_UNABLE_TO_APPROVE = 3; 109 110 /** 111 * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API. 112 * 113 * @hide 114 */ 115 public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1; 116 117 /** 118 * @hide 119 */ 120 @IntDef(prefix = {"ERROR_"}, value = { 121 ERROR_DOMAIN_SET_ID_INVALID, 122 ERROR_UNKNOWN_DOMAIN, 123 ERROR_UNABLE_TO_APPROVE, 124 }) 125 public @interface Error { 126 } 127 128 private final Context mContext; 129 130 private final IDomainVerificationManager mDomainVerificationManager; 131 132 /** 133 * System service to access the domain verification APIs. 134 * <p> 135 * Allows the approved domain verification agent on the device (the sole holder of {@link 136 * android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status of 137 * domains declared by applications in their AndroidManifest.xml, to allow them to open those 138 * links inside the app when selected by the user. This is done through querying {@link 139 * #getDomainVerificationInfo(String)} and calling {@link #setDomainVerificationStatus(UUID, 140 * Set, int)}. 141 * <p> 142 * Also allows the domain preference settings (holder of 143 * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) 144 * to update the preferences of the user, when they have chosen to explicitly allow an 145 * application to open links. This is done through querying 146 * {@link #getDomainVerificationUserState(String)} and calling 147 * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and 148 * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}. 149 * 150 * @hide 151 */ DomainVerificationManager(Context context, IDomainVerificationManager domainVerificationManager)152 public DomainVerificationManager(Context context, 153 IDomainVerificationManager domainVerificationManager) { 154 mContext = context; 155 mDomainVerificationManager = domainVerificationManager; 156 } 157 158 /** 159 * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is 160 * usually a heavy workload and should be done infrequently. 161 * 162 * @return the current snapshot of package names with valid autoVerify URLs. 163 * @hide 164 */ 165 @SystemApi 166 @NonNull 167 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) queryValidVerificationPackageNames()168 public List<String> queryValidVerificationPackageNames() { 169 try { 170 return mDomainVerificationManager.queryValidVerificationPackageNames(); 171 } catch (RemoteException e) { 172 throw e.rethrowFromSystemServer(); 173 } 174 } 175 176 /** 177 * Retrieves the domain verification state for a given package. 178 * 179 * @return the data for the package, or null if it does not declare any autoVerify domains 180 * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error 181 * and should not be re-tried except on a time scheduled basis. 182 * @hide 183 */ 184 @SystemApi 185 @Nullable 186 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) getDomainVerificationInfo(@onNull String packageName)187 public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) 188 throws NameNotFoundException { 189 try { 190 return mDomainVerificationManager.getDomainVerificationInfo(packageName); 191 } catch (Exception e) { 192 Exception converted = rethrow(e, packageName); 193 if (converted instanceof NameNotFoundException) { 194 throw (NameNotFoundException) converted; 195 } else if (converted instanceof RuntimeException) { 196 throw (RuntimeException) converted; 197 } else { 198 throw new RuntimeException(converted); 199 } 200 } 201 } 202 203 /** 204 * Change the verification status of the {@param domains} of the package associated with {@param 205 * domainSetId}. 206 * 207 * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. 208 * @param domains List of host names to change the state of. 209 * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. 210 * @return error code or {@link #STATUS_OK} if successful 211 * @throws NameNotFoundException If the ID is known to be good, but the package is 212 * unavailable. This may be because the package is installed on 213 * a volume that is no longer mounted. This error is 214 * unrecoverable until the package is available again, and 215 * should not be re-tried except on a time scheduled basis. 216 * @hide 217 */ 218 @CheckResult 219 @SystemApi 220 @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) setDomainVerificationStatus(@onNull UUID domainSetId, @NonNull Set<String> domains, int state)221 public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, 222 int state) throws NameNotFoundException { 223 validateInput(domainSetId, domains); 224 225 try { 226 return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), 227 new DomainSet(domains), state); 228 } catch (Exception e) { 229 Exception converted = rethrow(e, null); 230 if (converted instanceof NameNotFoundException) { 231 throw (NameNotFoundException) converted; 232 } else if (converted instanceof RuntimeException) { 233 throw (RuntimeException) converted; 234 } else { 235 throw new RuntimeException(converted); 236 } 237 } 238 } 239 240 /** 241 * Change whether the given packageName is allowed to handle BROWSABLE and DEFAULT category web 242 * (HTTP/HTTPS) {@link Intent} Activity open requests. The final state is determined along with 243 * the verification status for the specific domain being opened and other system state. An app 244 * with this enabled is not guaranteed to be the sole link handler for its domains. 245 * <p> 246 * By default, all apps are allowed to open links. Users must disable them explicitly. 247 * 248 * @hide 249 */ 250 @SystemApi 251 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) setDomainVerificationLinkHandlingAllowed(@onNull String packageName, boolean allowed)252 public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, 253 boolean allowed) throws NameNotFoundException { 254 try { 255 mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, 256 allowed, mContext.getUserId()); 257 } catch (Exception e) { 258 Exception converted = rethrow(e, null); 259 if (converted instanceof NameNotFoundException) { 260 throw (NameNotFoundException) converted; 261 } else if (converted instanceof RuntimeException) { 262 throw (RuntimeException) converted; 263 } else { 264 throw new RuntimeException(converted); 265 } 266 } 267 } 268 269 /** 270 * Update the recorded user selection for the given {@param domains} for the given {@param 271 * domainSetId}. This state is recorded for the lifetime of a domain for a package on device, 272 * and will never be reset by the system short of an app data clear. 273 * <p> 274 * This state is stored per device user. If another user needs to be changed, the appropriate 275 * permissions must be acquired and {@link Context#createContextAsUser(UserHandle, int)} should 276 * be used. 277 * <p> 278 * Enabling an unverified domain will allow an application to open it, but this can only occur 279 * if no other app on the device is approved for a higher approval level. This can queried 280 * using {@link #getOwnersForDomain(String)}. 281 * 282 * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to 283 * enable that domain will disable all other owners. 284 * 285 * On the other hand, if any of the owners are non-overrideable, then this must be called with 286 * false for all of the other owners to disable them before the domain can be taken by a new 287 * owner. 288 * 289 * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. 290 * @param domains The domains to toggle the state of. 291 * @param enabled Whether or not the app should automatically open the domains specified. 292 * @return error code or {@link #STATUS_OK} if successful 293 * @throws NameNotFoundException If the ID is known to be good, but the package is 294 * unavailable. This may be because the package is installed on 295 * a volume that is no longer mounted. This error is 296 * unrecoverable until the package is available again, and 297 * should not be re-tried except on a time scheduled basis. 298 * @hide 299 */ 300 @CheckResult 301 @SystemApi 302 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) setDomainVerificationUserSelection(@onNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled)303 public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, 304 @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { 305 validateInput(domainSetId, domains); 306 307 try { 308 return mDomainVerificationManager.setDomainVerificationUserSelection( 309 domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId()); 310 } catch (Exception e) { 311 Exception converted = rethrow(e, null); 312 if (converted instanceof NameNotFoundException) { 313 throw (NameNotFoundException) converted; 314 } else if (converted instanceof RuntimeException) { 315 throw (RuntimeException) converted; 316 } else { 317 throw new RuntimeException(converted); 318 } 319 } 320 } 321 322 /** 323 * Retrieve the user state for the given package and the {@link Context}'s user. 324 * 325 * @param packageName The app to query state for. 326 * @return The user selection verification data for the given package for the user, or null if 327 * the package does not declare any HTTP/HTTPS domains. 328 */ 329 @Nullable getDomainVerificationUserState( @onNull String packageName)330 public DomainVerificationUserState getDomainVerificationUserState( 331 @NonNull String packageName) throws NameNotFoundException { 332 try { 333 return mDomainVerificationManager.getDomainVerificationUserState(packageName, 334 mContext.getUserId()); 335 } catch (Exception e) { 336 Exception converted = rethrow(e, packageName); 337 if (converted instanceof NameNotFoundException) { 338 throw (NameNotFoundException) converted; 339 } else if (converted instanceof RuntimeException) { 340 throw (RuntimeException) converted; 341 } else { 342 throw new RuntimeException(converted); 343 } 344 } 345 } 346 347 /** 348 * For the given domain, return all apps which are approved to open it in a 349 * greater than 0 priority. This does not mean that all apps can actually open 350 * an Intent with that domain. That will be decided by the set of apps which 351 * are the highest priority level, ignoring all lower priority levels. 352 * 353 * The set will be ordered from lowest to highest priority. 354 * 355 * @param domain The host to query for. An invalid domain will result in an empty set. 356 * 357 * @hide 358 */ 359 @SystemApi 360 @NonNull 361 @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) getOwnersForDomain(@onNull String domain)362 public SortedSet<DomainOwner> getOwnersForDomain(@NonNull String domain) { 363 try { 364 Objects.requireNonNull(domain); 365 final List<DomainOwner> orderedList = mDomainVerificationManager.getOwnersForDomain( 366 domain, mContext.getUserId()); 367 SortedSet<DomainOwner> set = new TreeSet<>( 368 Comparator.comparingInt(orderedList::indexOf)); 369 set.addAll(orderedList); 370 return set; 371 } catch (RemoteException e) { 372 throw e.rethrowFromSystemServer(); 373 } 374 } 375 rethrow(Exception exception, @Nullable String packageName)376 private Exception rethrow(Exception exception, @Nullable String packageName) { 377 if (exception instanceof ServiceSpecificException) { 378 int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode; 379 if (packageName == null) { 380 packageName = exception.getMessage(); 381 } 382 383 if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) { 384 return new NameNotFoundException(packageName); 385 } 386 387 return exception; 388 } else if (exception instanceof RemoteException) { 389 return ((RemoteException) exception).rethrowFromSystemServer(); 390 } else { 391 return exception; 392 } 393 } 394 validateInput(@ullable UUID domainSetId, @Nullable Set<String> domains)395 private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) { 396 if (domainSetId == null) { 397 throw new IllegalArgumentException("domainSetId cannot be null"); 398 } else if (CollectionUtils.isEmpty(domains)) { 399 throw new IllegalArgumentException("Provided domain set cannot be empty"); 400 } 401 } 402 } 403