1 /*
2  * Copyright (C) 2022 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 com.android.server.pm;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.UserInfo;
26 import android.util.SparseBooleanArray;
27 
28 import com.android.internal.app.IntentForwarderActivity;
29 import com.android.internal.util.CollectionUtils;
30 import com.android.server.pm.pkg.PackageStateInternal;
31 import com.android.server.pm.resolution.ComponentResolverApi;
32 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.function.Function;
38 
39 /**
40  * Cross profile resolver used as default strategy. Primary known use-case for this resolver is
41  * work/managed profile .
42  */
43 public final class DefaultCrossProfileResolver extends CrossProfileResolver {
44 
45     private final DomainVerificationManagerInternal mDomainVerificationManager;
46 
47 
DefaultCrossProfileResolver(ComponentResolverApi componentResolver, UserManagerService userManager, DomainVerificationManagerInternal domainVerificationManager)48     public DefaultCrossProfileResolver(ComponentResolverApi componentResolver,
49             UserManagerService userManager,
50             DomainVerificationManagerInternal domainVerificationManager) {
51         super(componentResolver, userManager);
52         mDomainVerificationManager = domainVerificationManager;
53     }
54 
55     /**
56      * This is Default resolution strategy primarily used by Work Profile.
57      * First, it checks if we have to skip source profile and just resolve in target profile. If
58      * yes, then it will return result from target profile.
59      * Secondly, it find specific resolve infos in target profile
60      * Thirdly, if it is web intent it finds if parent can also resolve it. The results of this
61      * stage gets higher priority as compared to second stage.
62      *
63      * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
64      * @param intent request
65      * @param resolvedType the MIME data type of intent request
66      * @param userId source/initiating user
67      * @param targetUserId target user id
68      * @param flags of intent request
69      * @param pkgName the application package name this Intent is limited to.
70      * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
71      *                                                        targeting the targetUserId
72      * @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
73      *                                     resolveInfo in their profile.
74      * @param pkgSettingFunction function to find PackageStateInternal for given package
75      * @return list of {@link CrossProfileDomainInfo}
76      */
77     @Override
resolveIntent(Computer computer, Intent intent, String resolvedType, int userId, int targetUserId, long flags, String pkgName, List<CrossProfileIntentFilter> matchingFilters, boolean hasNonNegativePriorityResult, Function<String, PackageStateInternal> pkgSettingFunction)78     public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
79             String resolvedType, int userId, int targetUserId,
80             long flags, String pkgName, List<CrossProfileIntentFilter> matchingFilters,
81             boolean hasNonNegativePriorityResult,
82             Function<String, PackageStateInternal> pkgSettingFunction) {
83 
84         List<CrossProfileDomainInfo> xpResult = new ArrayList<>();
85         if (pkgName != null) return xpResult;
86         CrossProfileDomainInfo skipProfileInfo = querySkipCurrentProfileIntents(computer,
87                 matchingFilters, intent, resolvedType, flags, userId, pkgSettingFunction);
88 
89         if (skipProfileInfo != null) {
90             xpResult.add(skipProfileInfo);
91             return filterIfNotSystemUser(xpResult, userId);
92         }
93 
94         CrossProfileDomainInfo specificXpInfo = queryCrossProfileIntents(computer,
95                 matchingFilters, intent, resolvedType, flags, userId,
96                 hasNonNegativePriorityResult, pkgSettingFunction);
97 
98         if (intent.hasWebURI()) {
99             CrossProfileDomainInfo generalXpInfo = null;
100             final UserInfo parent = getProfileParent(userId);
101             if (parent != null) {
102                 generalXpInfo = computer.getCrossProfileDomainPreferredLpr(intent, resolvedType,
103                         flags, userId, parent.id);
104             }
105             CrossProfileDomainInfo prioritizedXpInfo =
106                     generalXpInfo != null ? generalXpInfo : specificXpInfo;
107             if (prioritizedXpInfo != null) {
108                 xpResult.add(prioritizedXpInfo);
109             }
110         } else if (specificXpInfo != null) {
111             xpResult.add(specificXpInfo);
112         }
113 
114         return xpResult;
115     }
116 
117     /**
118      * Filters out CrossProfileDomainInfo if it does not have higher approval level as compared to
119      * given approval level
120      * @param intent request
121      * @param crossProfileDomainInfos resolved in target user
122      * @param flags for intent resolution
123      * @param sourceUserId source user
124      * @param targetUserId target user
125      * @param highestApprovalLevel highest level of domain approval
126      * @return filtered list of CrossProfileDomainInfo
127      */
128     @Override
filterResolveInfoWithDomainPreferredActivity( Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId, int targetUserId, int highestApprovalLevel)129     public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(
130             Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags,
131             int sourceUserId, int targetUserId, int highestApprovalLevel) {
132 
133         List<CrossProfileDomainInfo> filteredCrossProfileDomainInfos = new ArrayList<>();
134 
135         if (crossProfileDomainInfos != null && !crossProfileDomainInfos.isEmpty()) {
136             for (int index = 0; index < crossProfileDomainInfos.size(); index++) {
137                 CrossProfileDomainInfo crossProfileDomainInfo = crossProfileDomainInfos.get(index);
138                 if (crossProfileDomainInfo.mHighestApprovalLevel > highestApprovalLevel) {
139                     filteredCrossProfileDomainInfos.add(crossProfileDomainInfo);
140                 }
141             }
142         }
143 
144         return filteredCrossProfileDomainInfos;
145     }
146 
147     /**
148      * If current/source profile needs to be skipped, returns CrossProfileDomainInfo from target
149      * profile. If any of the matchingFilters have flag {@link PackageManager#SKIP_CURRENT_PROFILE}
150      * set that would signify that current profile needs to be skipped.
151      * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
152      * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
153      *                                                        targeting the targetUserId
154      * @param intent request
155      * @param resolvedType the MIME data type of intent request
156      * @param flags for intent resolution
157      * @param sourceUserId source user
158      * @param pkgSettingFunction function to find PackageStateInternal for given package
159      * @return CrossProfileDomainInfo if current profile needs to be skipped, else null
160      */
161     @Nullable
querySkipCurrentProfileIntents(Computer computer, List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, long flags, int sourceUserId, Function<String, PackageStateInternal> pkgSettingFunction)162     private CrossProfileDomainInfo querySkipCurrentProfileIntents(Computer computer,
163             List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
164             long flags, int sourceUserId,
165             Function<String, PackageStateInternal> pkgSettingFunction) {
166         if (matchingFilters != null) {
167             int size = matchingFilters.size();
168             for (int i = 0; i < size; i++) {
169                 CrossProfileIntentFilter filter = matchingFilters.get(i);
170                 if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
171                     // Checking if there are activities in the target user that can handle the
172                     // intent.
173                     CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter,
174                             intent, resolvedType, flags, sourceUserId, pkgSettingFunction);
175                     if (info != null) {
176                         return info;
177                     }
178                 }
179             }
180         }
181         return null;
182     }
183 
184     /**
185      * Resolves and returns CrossProfileDomainInfo(ForwardingResolveInfo) from target profile if
186      * current profile should be skipped when there is no result or if target profile should not
187      * be skipped.
188      *
189      * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
190      * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
191      *                                                        targeting the targetUserId
192      * @param intent request
193      * @param resolvedType the MIME data type of intent request
194      * @param flags for intent resolution
195      * @param sourceUserId source user
196      * @param matchInCurrentProfile true if current/source profile have some non-negative
197      *                              resolveInfo
198      * @param pkgSettingFunction function to find PackageStateInternal for given package
199      * @return CrossProfileDomainInfo returns forwarding intent resolver in CrossProfileDomainInfo.
200      * It returns null if there are no matching filters or no valid/active activity available
201      */
202     @Nullable
queryCrossProfileIntents(Computer computer, List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, long flags, int sourceUserId, boolean matchInCurrentProfile, Function<String, PackageStateInternal> pkgSettingFunction)203     private CrossProfileDomainInfo queryCrossProfileIntents(Computer computer,
204             List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
205             long flags, int sourceUserId, boolean matchInCurrentProfile,
206             Function<String, PackageStateInternal> pkgSettingFunction) {
207         if (matchingFilters == null) {
208             return null;
209         }
210         // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
211         // match the same intent. For performance reasons, it is better not to
212         // run queryIntent twice for the same userId
213         SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
214 
215         CrossProfileDomainInfo resultInfo = null;
216 
217         int size = matchingFilters.size();
218         for (int i = 0; i < size; i++) {
219             CrossProfileIntentFilter filter = matchingFilters.get(i);
220             int targetUserId = filter.getTargetUserId();
221             boolean skipCurrentProfile =
222                     (filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
223             boolean skipCurrentProfileIfNoMatchFound =
224                     (filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
225             if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
226                     && (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
227                 // Checking if there are activities in the target user that can handle the
228                 // intent.
229                 CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter, intent,
230                         resolvedType, flags, sourceUserId, pkgSettingFunction);
231                 if (info != null) {
232                     resultInfo = info;
233                     break;
234                 }
235                 alreadyTriedUserIds.put(targetUserId, true);
236             }
237         }
238 
239         if (resultInfo == null) {
240             return null;
241         }
242 
243         ResolveInfo forwardingResolveInfo = resultInfo.mResolveInfo;
244         if (!isUserEnabled(forwardingResolveInfo.targetUserId)) {
245             return null;
246         }
247 
248         List<CrossProfileDomainInfo> filteredResult =
249                 filterIfNotSystemUser(Collections.singletonList(resultInfo), sourceUserId);
250         if (filteredResult.isEmpty()) {
251             return null;
252         }
253 
254         return resultInfo;
255     }
256 
257     /**
258      * Creates a Forwarding Resolve Info, used when we have to signify that target profile's
259      * resolveInfo should be considered without providing list of resolve infos.
260      * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
261      * @param filter {@link CrossProfileIntentFilter} configured for source user,
262      *                                                        targeting the targetUserId
263      * @param intent request
264      * @param resolvedType the MIME data type of intent request
265      * @param flags for intent resolution
266      * @param sourceUserId source user
267      * @return CrossProfileDomainInfo whose ResolveInfo is forwarding. It would be resolved by
268      * {@link IntentForwarderActivity}. It returns null if there are no valid/active activities
269      */
270     @Nullable
createForwardingResolveInfo(Computer computer, @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent, @Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction)271     protected CrossProfileDomainInfo createForwardingResolveInfo(Computer computer,
272             @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent,
273             @Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
274             int sourceUserId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
275         int targetUserId = filter.getTargetUserId();
276         if (!isUserEnabled(targetUserId)) {
277             return null;
278         }
279 
280         List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent,
281                 resolvedType, flags, targetUserId);
282         if (CollectionUtils.isEmpty(resultTargetUser)) {
283             return null;
284         }
285 
286         ResolveInfo forwardingInfo = null;
287         for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
288             ResolveInfo targetUserResolveInfo = resultTargetUser.get(i);
289             if ((targetUserResolveInfo.activityInfo.applicationInfo.flags
290                     & ApplicationInfo.FLAG_SUSPENDED) == 0) {
291                 forwardingInfo = computer.createForwardingResolveInfoUnchecked(filter, sourceUserId,
292                         targetUserId);
293                 break;
294             }
295         }
296 
297         if (forwardingInfo == null) {
298             // If all the matches in the target profile are suspended, return null.
299             return null;
300         }
301 
302         int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
303 
304         int size = resultTargetUser.size();
305         for (int i = 0; i < size; i++) {
306             ResolveInfo riTargetUser = resultTargetUser.get(i);
307             if (riTargetUser.handleAllWebDataURI) {
308                 continue;
309             }
310             String packageName = riTargetUser.activityInfo.packageName;
311             PackageStateInternal ps = pkgSettingFunction.apply(packageName);
312             if (ps == null) {
313                 continue;
314             }
315             highestApprovalLevel = Math.max(highestApprovalLevel, mDomainVerificationManager
316                     .approvalLevelForDomain(ps, intent, flags, targetUserId));
317         }
318 
319         return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel, targetUserId);
320     }
321 }
322