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