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