Merge "Creating CrossProfileResolverEngine which would facilitate all cross profile intent resolution"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a44cf8829d
@@ -91,7 +91,7 @@ import java.util.Set;
|
||||
* other hand, not overriding in {@link ComputerLocked} may leave a function walking
|
||||
* unstable data.
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
public interface Computer extends PackageDataSnapshot {
|
||||
|
||||
int getVersion();
|
||||
|
||||
@@ -30,7 +30,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
|
||||
import static android.content.pm.PackageManager.MATCH_ALL;
|
||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
import static android.content.pm.PackageManager.MATCH_APEX;
|
||||
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
|
||||
@@ -49,7 +48,6 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
|
||||
|
||||
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
|
||||
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_INFO;
|
||||
@@ -116,7 +114,6 @@ import android.util.MathUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
@@ -145,7 +142,6 @@ import com.android.server.pm.pkg.component.ParsedProvider;
|
||||
import com.android.server.pm.pkg.component.ParsedService;
|
||||
import com.android.server.pm.resolution.ComponentResolverApi;
|
||||
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
|
||||
import com.android.server.pm.verify.domain.DomainVerificationUtils;
|
||||
import com.android.server.uri.UriGrantsManagerInternal;
|
||||
import com.android.server.utils.WatchedArrayMap;
|
||||
import com.android.server.utils.WatchedLongSparseArray;
|
||||
@@ -411,6 +407,7 @@ public class ComputerEngine implements Computer {
|
||||
private final CompilerStats mCompilerStats;
|
||||
private final BackgroundDexOptService mBackgroundDexOptService;
|
||||
private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
|
||||
private final CrossProfileIntentResolverEngine mCrossProfileIntentResolverEngine;
|
||||
|
||||
// PackageManagerService attributes that are primitives are referenced through the
|
||||
// pms object directly. Primitives are the only attributes so referenced.
|
||||
@@ -464,6 +461,8 @@ public class ComputerEngine implements Computer {
|
||||
mCompilerStats = args.service.mCompilerStats;
|
||||
mBackgroundDexOptService = args.service.mBackgroundDexOptService;
|
||||
mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
|
||||
mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
|
||||
mUserManager, mDomainVerificationManager, mDefaultAppProvider);
|
||||
|
||||
// Used to reference PMS attributes that are primitives and which are not
|
||||
// updated under control of the PMS lock.
|
||||
@@ -735,101 +734,72 @@ public class ComputerEngine implements Computer {
|
||||
// reader
|
||||
boolean sortResult = false;
|
||||
boolean addInstant = false;
|
||||
List<ResolveInfo> result = null;
|
||||
List<ResolveInfo> result = new ArrayList<>();
|
||||
// crossProfileResults will hold resolve infos from resolution across profiles.
|
||||
List<CrossProfileDomainInfo> crossProfileResults = new ArrayList<>();
|
||||
if (pkgName == null) {
|
||||
List<CrossProfileIntentFilter> matchingFilters =
|
||||
getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
|
||||
// Check for results that need to skip the current profile.
|
||||
ResolveInfo skipProfileInfo = querySkipCurrentProfileIntents(matchingFilters,
|
||||
intent, resolvedType, flags, userId);
|
||||
if (skipProfileInfo != null) {
|
||||
List<ResolveInfo> xpResult = new ArrayList<>(1);
|
||||
xpResult.add(skipProfileInfo);
|
||||
return new QueryIntentActivitiesResult(
|
||||
applyPostResolutionFilter(
|
||||
filterIfNotSystemUser(xpResult, userId), instantAppPkgName,
|
||||
allowDynamicSplits, filterCallingUid, resolveForStart, userId,
|
||||
intent));
|
||||
if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
|
||||
resolvedType, userId)) {
|
||||
/*
|
||||
Check for results in the current profile only if there is no
|
||||
{@link CrossProfileIntentFilter} for user with flag
|
||||
{@link PackageManager.SKIP_CURRENT_PROFILE} set.
|
||||
*/
|
||||
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
|
||||
intent, resolvedType, flags, userId), userId));
|
||||
}
|
||||
|
||||
// Check for results in the current profile.
|
||||
result = filterIfNotSystemUser(mComponentResolver.queryActivities(this,
|
||||
intent, resolvedType, flags, userId), userId);
|
||||
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
|
||||
false /*skipPackageCheck*/, flags);
|
||||
// Check for cross profile results.
|
||||
|
||||
boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
|
||||
CrossProfileDomainInfo specificXpInfo = queryCrossProfileIntents(
|
||||
matchingFilters, intent, resolvedType, flags, userId,
|
||||
hasNonNegativePriorityResult);
|
||||
if (intent.hasWebURI()) {
|
||||
CrossProfileDomainInfo generalXpInfo = null;
|
||||
final UserInfo parent = getProfileParent(userId);
|
||||
if (parent != null) {
|
||||
generalXpInfo = getCrossProfileDomainPreferredLpr(intent, resolvedType,
|
||||
flags, userId, parent.id);
|
||||
}
|
||||
|
||||
// Generalized cross profile intents take precedence over specific.
|
||||
// Note that this is the opposite of the intuitive order.
|
||||
CrossProfileDomainInfo prioritizedXpInfo =
|
||||
generalXpInfo != null ? generalXpInfo : specificXpInfo;
|
||||
|
||||
if (!addInstant) {
|
||||
if (result.isEmpty() && prioritizedXpInfo != null) {
|
||||
// No result in current profile, but found candidate in parent user.
|
||||
// And we are not going to add ephemeral app, so we can return the
|
||||
// result straight away.
|
||||
result.add(prioritizedXpInfo.mResolveInfo);
|
||||
return new QueryIntentActivitiesResult(
|
||||
applyPostResolutionFilter(result, instantAppPkgName,
|
||||
allowDynamicSplits, filterCallingUid, resolveForStart,
|
||||
userId, intent));
|
||||
} else if (result.size() <= 1 && prioritizedXpInfo == null) {
|
||||
// No result in parent user and <= 1 result in current profile, and we
|
||||
// are not going to add ephemeral app, so we can return the result
|
||||
// without further processing.
|
||||
return new QueryIntentActivitiesResult(
|
||||
applyPostResolutionFilter(result, instantAppPkgName,
|
||||
allowDynamicSplits, filterCallingUid, resolveForStart,
|
||||
userId, intent));
|
||||
}
|
||||
}
|
||||
|
||||
// We have more than one candidate (combining results from current and parent
|
||||
// profile), so we need filtering and sorting.
|
||||
result = filterCandidatesWithDomainPreferredActivitiesLPr(
|
||||
intent, flags, result, prioritizedXpInfo, userId);
|
||||
sortResult = true;
|
||||
} else {
|
||||
// If not web Intent, just add result to candidate set and let ResolverActivity
|
||||
// figure it out.
|
||||
if (specificXpInfo != null) {
|
||||
result.add(specificXpInfo.mResolveInfo);
|
||||
sortResult = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
Calling {@link com.android.server.pm.CrossProfileIntentResolverEngine#resolveIntent} to
|
||||
get list of {@link CrossProfileDomainInfo} which have {@link ResolveInfo}s from linked
|
||||
profiles.
|
||||
*/
|
||||
crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
|
||||
resolvedType, userId, flags, pkgName, hasNonNegativePriorityResult,
|
||||
mSettings::getPackage);
|
||||
if (intent.hasWebURI() || !crossProfileResults.isEmpty()) sortResult = true;
|
||||
} else {
|
||||
final PackageStateInternal setting =
|
||||
getPackageStateInternal(pkgName, Process.SYSTEM_UID);
|
||||
result = null;
|
||||
|
||||
if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
|
||||
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
|
||||
result = filterIfNotSystemUser(mComponentResolver.queryActivities(this,
|
||||
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
|
||||
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
|
||||
userId), userId);
|
||||
userId), userId));
|
||||
}
|
||||
if (result == null || result.size() == 0) {
|
||||
// the caller wants to resolve for a particular package; however, there
|
||||
// were no installed results, so, try to find an ephemeral result
|
||||
addInstant = isInstantAppResolutionAllowed(intent, null /*result*/, userId,
|
||||
true /*skipPackageCheck*/, flags);
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
/*
|
||||
Calling {@link com.android.server.pm.CrossProfileIntentResolverEngine#resolveIntent} to
|
||||
get list of {@link CrossProfileDomainInfo} which have {@link ResolveInfo}s from linked
|
||||
profiles.
|
||||
*/
|
||||
crossProfileResults = mCrossProfileIntentResolverEngine.resolveIntent(this, intent,
|
||||
resolvedType, userId, flags, pkgName, false,
|
||||
mSettings::getPackage);
|
||||
}
|
||||
return new QueryIntentActivitiesResult(sortResult, addInstant, result);
|
||||
|
||||
/*
|
||||
Calling {@link com.android.server.pm.
|
||||
CrossProfileIntentResolverEngine#combineFilterAndCreateQueryAcitivitesResponse} to
|
||||
combine results from current and cross profiles. This also filters any resolve info
|
||||
based on domain preference(if required).
|
||||
*/
|
||||
return mCrossProfileIntentResolverEngine
|
||||
.combineFilterAndCreateQueryActivitiesResponse(this, intent, resolvedType,
|
||||
instantAppPkgName, pkgName, allowDynamicSplits, flags, userId,
|
||||
filterCallingUid, resolveForStart, result, crossProfileResults,
|
||||
areWebInstantAppsDisabled(userId), addInstant, sortResult,
|
||||
mSettings::getPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1046,126 +1016,6 @@ public class ComputerEngine implements Computer {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
|
||||
Intent intent, long matchFlags, List<ResolveInfo> candidates,
|
||||
CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
|
||||
final ArrayList<ResolveInfo> result = new ArrayList<>();
|
||||
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
|
||||
final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
|
||||
|
||||
// Blocking instant apps is usually done in applyPostResolutionFilter, but since
|
||||
// domain verification can resolve to a single result, which can be an instant app,
|
||||
// it will then be filtered to an empty list in that method. Instead, do blocking
|
||||
// here so that instant apps can be ignored for approval filtering and a lower
|
||||
// priority result chosen instead.
|
||||
final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId);
|
||||
|
||||
final int count = candidates.size();
|
||||
// First, try to use approved apps.
|
||||
for (int n = 0; n < count; n++) {
|
||||
ResolveInfo info = candidates.get(n);
|
||||
if (blockInstant && (info.isInstantAppAvailable
|
||||
|| isInstantAppInternal(info.activityInfo.packageName, userId,
|
||||
Process.SYSTEM_UID))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to the special match all list (Browser use case)
|
||||
if (info.handleAllWebDataURI) {
|
||||
matchAllList.add(info);
|
||||
} else {
|
||||
undefinedList.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
// We'll want to include browser possibilities in a few cases
|
||||
boolean includeBrowser = false;
|
||||
|
||||
if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) {
|
||||
result.addAll(undefinedList);
|
||||
// Maybe add one for the other profile.
|
||||
if (xpDomainInfo != null && xpDomainInfo.mHighestApprovalLevel
|
||||
> DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
|
||||
result.add(xpDomainInfo.mResolveInfo);
|
||||
}
|
||||
includeBrowser = true;
|
||||
} else {
|
||||
Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
|
||||
.filterToApprovedApp(intent, undefinedList, userId,
|
||||
mSettings::getPackage);
|
||||
List<ResolveInfo> approvedInfos = infosAndLevel.first;
|
||||
Integer highestApproval = infosAndLevel.second;
|
||||
|
||||
// If no apps are approved for the domain, resolve only to browsers
|
||||
if (approvedInfos.isEmpty()) {
|
||||
includeBrowser = true;
|
||||
if (xpDomainInfo != null && xpDomainInfo.mHighestApprovalLevel
|
||||
> DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
|
||||
result.add(xpDomainInfo.mResolveInfo);
|
||||
}
|
||||
} else {
|
||||
result.addAll(approvedInfos);
|
||||
|
||||
// If the other profile has an app that's higher approval, add it
|
||||
if (xpDomainInfo != null
|
||||
&& xpDomainInfo.mHighestApprovalLevel > highestApproval) {
|
||||
result.add(xpDomainInfo.mResolveInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeBrowser) {
|
||||
// Also add browsers (all of them or only the default one)
|
||||
if (DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, " ...including browsers in candidate set");
|
||||
}
|
||||
if ((matchFlags & MATCH_ALL) != 0) {
|
||||
result.addAll(matchAllList);
|
||||
} else {
|
||||
// Browser/generic handling case. If there's a default browser, go straight
|
||||
// to that (but only if there is no other higher-priority match).
|
||||
final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(
|
||||
userId);
|
||||
int maxMatchPrio = 0;
|
||||
ResolveInfo defaultBrowserMatch = null;
|
||||
final int numCandidates = matchAllList.size();
|
||||
for (int n = 0; n < numCandidates; n++) {
|
||||
ResolveInfo info = matchAllList.get(n);
|
||||
// track the highest overall match priority...
|
||||
if (info.priority > maxMatchPrio) {
|
||||
maxMatchPrio = info.priority;
|
||||
}
|
||||
// ...and the highest-priority default browser match
|
||||
if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
|
||||
if (defaultBrowserMatch == null
|
||||
|| (defaultBrowserMatch.priority < info.priority)) {
|
||||
if (debug) {
|
||||
Slog.v(TAG, "Considering default browser match " + info);
|
||||
}
|
||||
defaultBrowserMatch = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defaultBrowserMatch != null
|
||||
&& defaultBrowserMatch.priority >= maxMatchPrio
|
||||
&& !TextUtils.isEmpty(defaultBrowserPackageName)) {
|
||||
if (debug) {
|
||||
Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
|
||||
}
|
||||
result.add(defaultBrowserMatch);
|
||||
} else {
|
||||
result.addAll(matchAllList);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is nothing selected, add all candidates
|
||||
if (result.size() == 0) {
|
||||
result.addAll(candidates);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the 'Home' activity which is currently set as "always use this one". If non is set
|
||||
* then reports the most likely home activity or null if there are more than one.
|
||||
@@ -1279,7 +1129,8 @@ public class ComputerEngine implements Computer {
|
||||
|
||||
if (result == null) {
|
||||
result = new CrossProfileDomainInfo(createForwardingResolveInfoUnchecked(
|
||||
new WatchedIntentFilter(), sourceUserId, parentUserId), approvalLevel);
|
||||
new WatchedIntentFilter(), sourceUserId, parentUserId), approvalLevel,
|
||||
parentUserId);
|
||||
} else {
|
||||
result.mHighestApprovalLevel =
|
||||
Math.max(approvalLevel, result.mHighestApprovalLevel);
|
||||
@@ -1303,7 +1154,8 @@ public class ComputerEngine implements Computer {
|
||||
Intent intent, String resolvedType, int userId) {
|
||||
CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId);
|
||||
if (resolver != null) {
|
||||
return resolver.queryIntent(this, intent, resolvedType, false /*defaultOnly*/, userId);
|
||||
return resolver.queryIntent(this, intent, resolvedType, false /*defaultOnly*/,
|
||||
userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1467,30 +1319,6 @@ public class ComputerEngine implements Computer {
|
||||
return resolveInfos;
|
||||
}
|
||||
|
||||
private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
|
||||
long matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
|
||||
int userId) {
|
||||
final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
|
||||
|
||||
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, "Filtering results with preferred activities. Candidates count: "
|
||||
+ candidates.size());
|
||||
}
|
||||
|
||||
final ArrayList<ResolveInfo> result =
|
||||
filterCandidatesWithDomainPreferredActivitiesLPrBody(
|
||||
intent, matchFlags, candidates, xpDomainInfo, userId, debug);
|
||||
|
||||
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, "Filtered results with preferred activities. New candidates count: "
|
||||
+ result.size());
|
||||
for (ResolveInfo info : result) {
|
||||
Slog.v(TAG, " + " + info.activityInfo);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out activities with systemUserOnly flag set, when current user is not System.
|
||||
*
|
||||
@@ -1930,63 +1758,6 @@ public class ComputerEngine implements Computer {
|
||||
return new ParceledListSlice<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the filter's target user can handle the intent and is enabled: a [ResolveInfo] that
|
||||
* will forward the intent to the filter's target user, along with the highest approval of
|
||||
* any handler in the target user. Otherwise, returns null.
|
||||
*/
|
||||
@Nullable
|
||||
private CrossProfileDomainInfo createForwardingResolveInfo(
|
||||
@NonNull CrossProfileIntentFilter filter, @NonNull Intent intent,
|
||||
@Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
|
||||
int sourceUserId) {
|
||||
int targetUserId = filter.getTargetUserId();
|
||||
if (!isUserEnabled(targetUserId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
|
||||
resolvedType, flags, targetUserId);
|
||||
if (CollectionUtils.isEmpty(resultTargetUser)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResolveInfo forwardingInfo = null;
|
||||
for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
|
||||
ResolveInfo targetUserResolveInfo = resultTargetUser.get(i);
|
||||
if ((targetUserResolveInfo.activityInfo.applicationInfo.flags
|
||||
& ApplicationInfo.FLAG_SUSPENDED) == 0) {
|
||||
forwardingInfo = createForwardingResolveInfoUnchecked(filter, sourceUserId,
|
||||
targetUserId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardingInfo == null) {
|
||||
// If all the matches in the target profile are suspended, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
|
||||
|
||||
int size = resultTargetUser.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ResolveInfo riTargetUser = resultTargetUser.get(i);
|
||||
if (riTargetUser.handleAllWebDataURI) {
|
||||
continue;
|
||||
}
|
||||
String packageName = riTargetUser.activityInfo.packageName;
|
||||
PackageStateInternal ps = mSettings.getPackage(packageName);
|
||||
if (ps == null) {
|
||||
continue;
|
||||
}
|
||||
highestApprovalLevel = Math.max(highestApprovalLevel, mDomainVerificationManager
|
||||
.approvalLevelForDomain(ps, intent, flags, targetUserId));
|
||||
}
|
||||
|
||||
return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel);
|
||||
}
|
||||
|
||||
public final ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
|
||||
int sourceUserId, int targetUserId) {
|
||||
ResolveInfo forwardingResolveInfo = new ResolveInfo();
|
||||
@@ -2022,83 +1793,6 @@ public class ComputerEngine implements Computer {
|
||||
return forwardingResolveInfo;
|
||||
}
|
||||
|
||||
// Return matching ResolveInfo in target user if any.
|
||||
@Nullable
|
||||
private CrossProfileDomainInfo queryCrossProfileIntents(
|
||||
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
|
||||
long flags, int sourceUserId, boolean matchInCurrentProfile) {
|
||||
if (matchingFilters == null) {
|
||||
return null;
|
||||
}
|
||||
// Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
|
||||
// match the same intent. For performance reasons, it is better not to
|
||||
// run queryIntent twice for the same userId
|
||||
SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
|
||||
|
||||
CrossProfileDomainInfo resultInfo = null;
|
||||
|
||||
int size = matchingFilters.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
CrossProfileIntentFilter filter = matchingFilters.get(i);
|
||||
int targetUserId = filter.getTargetUserId();
|
||||
boolean skipCurrentProfile =
|
||||
(filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
|
||||
boolean skipCurrentProfileIfNoMatchFound =
|
||||
(filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
|
||||
if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
|
||||
&& (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
|
||||
// Checking if there are activities in the target user that can handle the
|
||||
// intent.
|
||||
CrossProfileDomainInfo info = createForwardingResolveInfo(filter, intent,
|
||||
resolvedType, flags, sourceUserId);
|
||||
if (info != null) {
|
||||
resultInfo = info;
|
||||
break;
|
||||
}
|
||||
alreadyTriedUserIds.put(targetUserId, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResolveInfo forwardingResolveInfo = resultInfo.mResolveInfo;
|
||||
if (!isUserEnabled(forwardingResolveInfo.targetUserId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ResolveInfo> filteredResult =
|
||||
filterIfNotSystemUser(Collections.singletonList(forwardingResolveInfo),
|
||||
sourceUserId);
|
||||
if (filteredResult.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultInfo;
|
||||
}
|
||||
|
||||
private ResolveInfo querySkipCurrentProfileIntents(
|
||||
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
|
||||
long flags, int sourceUserId) {
|
||||
if (matchingFilters != null) {
|
||||
int size = matchingFilters.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
CrossProfileIntentFilter filter = matchingFilters.get(i);
|
||||
if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
|
||||
// Checking if there are activities in the target user that can handle the
|
||||
// intent.
|
||||
CrossProfileDomainInfo info = createForwardingResolveInfo(filter, intent,
|
||||
resolvedType, flags, sourceUserId);
|
||||
if (info != null) {
|
||||
return info.mResolveInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final ServiceInfo getServiceInfo(ComponentName component,
|
||||
@PackageManager.ResolveInfoFlagsBits long flags, int userId) {
|
||||
if (!mUserManager.exists(userId)) return null;
|
||||
@@ -2740,16 +2434,6 @@ public class ComputerEngine implements Computer {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUserEnabled(int userId) {
|
||||
final long callingId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
UserInfo userInfo = mUserManager.getUserInfo(userId);
|
||||
return userInfo != null && userInfo.isEnabled();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not access to the application should be filtered.
|
||||
* <p>
|
||||
@@ -5691,13 +5375,9 @@ public class ComputerEngine implements Computer {
|
||||
@UserIdInt int sourceUserId, @UserIdInt int targetUserId) {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
|
||||
List<CrossProfileIntentFilter> matches =
|
||||
getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
|
||||
if (matches != null) {
|
||||
int size = matches.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (matches.get(i).getTargetUserId() == targetUserId) return true;
|
||||
}
|
||||
if (mCrossProfileIntentResolverEngine.canReachTo(this, intent, resolvedType,
|
||||
sourceUserId, targetUserId)) {
|
||||
return true;
|
||||
}
|
||||
if (intent.hasWebURI()) {
|
||||
// cross-profile app linking works only towards the parent.
|
||||
|
||||
@@ -16,12 +16,23 @@
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.UserHandle;
|
||||
|
||||
public final class CrossProfileDomainInfo {
|
||||
/* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
|
||||
ResolveInfo mResolveInfo;
|
||||
int mHighestApprovalLevel;
|
||||
@UserIdInt
|
||||
int mTargetUserId = UserHandle.USER_CURRENT; // default as current user
|
||||
|
||||
CrossProfileDomainInfo(ResolveInfo resolveInfo, int highestApprovalLevel, @UserIdInt
|
||||
int targetUserId) {
|
||||
this.mResolveInfo = resolveInfo;
|
||||
this.mHighestApprovalLevel = highestApprovalLevel;
|
||||
this.mTargetUserId = targetUserId;
|
||||
}
|
||||
|
||||
CrossProfileDomainInfo(ResolveInfo resolveInfo, int highestApprovalLevel) {
|
||||
this.mResolveInfo = resolveInfo;
|
||||
@@ -33,6 +44,7 @@ public final class CrossProfileDomainInfo {
|
||||
return "CrossProfileDomainInfo{"
|
||||
+ "resolveInfo=" + mResolveInfo
|
||||
+ ", highestApprovalLevel=" + mHighestApprovalLevel
|
||||
+ ", targetUserId= " + mTargetUserId
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import static android.content.pm.PackageManager.MATCH_ALL;
|
||||
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
|
||||
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
|
||||
import static com.android.server.pm.PackageManagerService.TAG;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.pm.pkg.PackageStateInternal;
|
||||
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
|
||||
import com.android.server.pm.verify.domain.DomainVerificationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Rule based engine which decides strategy to be used for source,target pair and does cross profile
|
||||
* intent resolution. Currently, we have only default and clone strategy. The major known use-case
|
||||
* for default is work profile.
|
||||
*/
|
||||
public class CrossProfileIntentResolverEngine {
|
||||
|
||||
private final UserManagerService mUserManager;
|
||||
private final DomainVerificationManagerInternal mDomainVerificationManager;
|
||||
private final DefaultAppProvider mDefaultAppProvider;
|
||||
|
||||
public CrossProfileIntentResolverEngine(UserManagerService userManager,
|
||||
DomainVerificationManagerInternal domainVerificationManager,
|
||||
DefaultAppProvider defaultAppProvider) {
|
||||
mUserManager = userManager;
|
||||
mDomainVerificationManager = domainVerificationManager;
|
||||
mDefaultAppProvider = defaultAppProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of {@link CrossProfileDomainInfo} which contains {@link ResolveInfo} from
|
||||
* profiles linked directly/indirectly to user. Work-Owner as well as Clone-Owner
|
||||
* are directly related as they are child of owner. Work-Clone are indirectly linked through
|
||||
* owner profile.
|
||||
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param userId source user for which intent request is called
|
||||
* @param flags used for intent resolution
|
||||
* @param pkgName the application package name this Intent is limited to.
|
||||
* @param hasNonNegativePriorityResult signifies if current profile have any non-negative(active
|
||||
* and valid) ResolveInfo in current profile.
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of {@link CrossProfileDomainInfo} from linked profiles.
|
||||
*/
|
||||
public List<CrossProfileDomainInfo> resolveIntent(@NonNull Computer computer, Intent intent,
|
||||
String resolvedType, int userId, long flags, String pkgName,
|
||||
boolean hasNonNegativePriorityResult,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
return resolveIntentInternal(computer, intent, resolvedType, userId, flags, pkgName,
|
||||
hasNonNegativePriorityResult, pkgSettingFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves intent in directly linked profiles and return list of {@link CrossProfileDomainInfo}
|
||||
* which contains {@link ResolveInfo}. This would also recursively call profiles not directly
|
||||
* linked.
|
||||
*
|
||||
* It first finds {@link CrossProfileIntentFilter} configured in current profile to find list of
|
||||
* target user profiles that can serve current intent request. It uses corresponding strategy
|
||||
* for each pair (source,target) user to resolve intent from target profile and returns combined
|
||||
* results.
|
||||
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param userId source user for which intent request is called
|
||||
* @param flags used for intent resolution
|
||||
* @param pkgName the application package name this Intent is limited to.
|
||||
* @param hasNonNegativePriorityResult signifies if current profile have any non-negative(active
|
||||
* and valid) ResolveInfo in current profile.
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of {@link CrossProfileDomainInfo} from linked profiles.
|
||||
*/
|
||||
private List<CrossProfileDomainInfo> resolveIntentInternal(@NonNull Computer computer,
|
||||
Intent intent, String resolvedType, int userId, long flags, String pkgName,
|
||||
boolean hasNonNegativePriorityResult,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
|
||||
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
|
||||
|
||||
List<CrossProfileIntentFilter> matchingFilters =
|
||||
computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
|
||||
|
||||
if (matchingFilters == null || matchingFilters.isEmpty()) return crossProfileDomainInfos;
|
||||
|
||||
UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
|
||||
UserInfo sourceUserInfo = umInternal.getUserInfo(userId);
|
||||
|
||||
// Grouping the CrossProfileIntentFilters based on targerId
|
||||
SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
|
||||
new SparseArray<>();
|
||||
|
||||
for (int index = 0; index < matchingFilters.size(); index++) {
|
||||
CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
|
||||
|
||||
if (!crossProfileIntentFiltersByUser
|
||||
.contains(crossProfileIntentFilter.mTargetUserId)) {
|
||||
crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
|
||||
new ArrayList<>());
|
||||
}
|
||||
crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
|
||||
.add(crossProfileIntentFilter);
|
||||
}
|
||||
|
||||
/*
|
||||
For each target user, we would call their corresponding strategy
|
||||
{@link CrossProfileResolver} to resolve intent in corresponding user
|
||||
*/
|
||||
for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
|
||||
|
||||
UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser
|
||||
.keyAt(index));
|
||||
|
||||
// Choosing strategy based on source and target user
|
||||
CrossProfileResolver crossProfileResolver =
|
||||
chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
|
||||
|
||||
/*
|
||||
If {@link CrossProfileResolver} is available for source,target pair we will call it to
|
||||
get {@link CrossProfileDomainInfo}s from that user.
|
||||
*/
|
||||
if (crossProfileResolver != null) {
|
||||
List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
|
||||
.resolveIntent(computer, intent, resolvedType, userId,
|
||||
crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName,
|
||||
crossProfileIntentFiltersByUser.valueAt(index),
|
||||
hasNonNegativePriorityResult, pkgSettingFunction);
|
||||
crossProfileDomainInfos.addAll(crossProfileInfos);
|
||||
}
|
||||
}
|
||||
|
||||
return crossProfileDomainInfos;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@link CrossProfileResolver} strategy based on source and target user
|
||||
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
|
||||
* @param sourceUserInfo source user
|
||||
* @param targetUserInfo target user
|
||||
* @return {@code CrossProfileResolver} which has value if source and target have
|
||||
* strategy configured otherwise null.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
|
||||
UserInfo sourceUserInfo, UserInfo targetUserInfo) {
|
||||
return new DefaultCrossProfileResolver(computer.getComponentResolver(),
|
||||
mUserManager, mDomainVerificationManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we source user can reach target user for given intent. The source can
|
||||
* directly or indirectly reach to target.
|
||||
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param sourceUserId source user
|
||||
* @param targetUserId target user
|
||||
* @return true if we source user can reach target user for given intent
|
||||
*/
|
||||
public boolean canReachTo(@NonNull Computer computer, @NonNull Intent intent,
|
||||
@Nullable String resolvedType, @UserIdInt int sourceUserId,
|
||||
@UserIdInt int targetUserId) {
|
||||
return canReachToInternal(computer, intent, resolvedType, sourceUserId, targetUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we source user can reach target user for given intent. The source can
|
||||
* directly or indirectly reach to target. This will perform depth first search to check if
|
||||
* source can reach target.
|
||||
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param sourceUserId source user
|
||||
* @param targetUserId target user
|
||||
* @return true if we source user can reach target user for given intent
|
||||
*/
|
||||
private boolean canReachToInternal(@NonNull Computer computer, @NonNull Intent intent,
|
||||
@Nullable String resolvedType, @UserIdInt int sourceUserId,
|
||||
@UserIdInt int targetUserId) {
|
||||
if (sourceUserId == targetUserId) return true;
|
||||
|
||||
List<CrossProfileIntentFilter> matches =
|
||||
computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
|
||||
if (matches != null) {
|
||||
for (int index = 0; index < matches.size(); index++) {
|
||||
CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
|
||||
if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of the matching {@link CrossProfileIntentFilter} suggest we should skip the
|
||||
* current profile based on flag {@link PackageManager#SKIP_CURRENT_PROFILE}.
|
||||
* @param computer {@link Computer} instance used to find {@link CrossProfileIntentFilter}
|
||||
* for user
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param sourceUserId id of initiating user space
|
||||
* @return boolean if we should skip resolution in current/source profile.
|
||||
*/
|
||||
public boolean shouldSkipCurrentProfile(Computer computer, Intent intent, String resolvedType,
|
||||
int sourceUserId) {
|
||||
List<CrossProfileIntentFilter> matches =
|
||||
computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
|
||||
if (matches != null) {
|
||||
for (int matchIndex = 0; matchIndex < matches.size(); matchIndex++) {
|
||||
CrossProfileIntentFilter crossProfileIntentFilter = matches.get(matchIndex);
|
||||
if ((crossProfileIntentFilter.getFlags()
|
||||
& PackageManager.SKIP_CURRENT_PROFILE) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines result from current and cross profile. This also does filtering based on domain(if
|
||||
* required).
|
||||
* @param computer {@link Computer} instance
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param instantAppPkgName package name if instant app is allowed
|
||||
* @param pkgName the application package name this Intent is limited to.
|
||||
* @param allowDynamicSplits true if dynamic splits is allowed
|
||||
* @param matchFlags flags for intent request
|
||||
* @param userId user id of source user
|
||||
* @param filterCallingUid uid of calling process
|
||||
* @param resolveForStart true if resolution occurs because an application is starting
|
||||
* @param candidates resolveInfos from current profile
|
||||
* @param crossProfileCandidates crossProfileDomainInfos from cross profile, it has ResolveInfo
|
||||
* @param areWebInstantAppsDisabled true if web instant apps are disabled
|
||||
* @param addInstant true if instant apps are allowed
|
||||
* @param sortResult true if caller would need to sort the results
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return QueryIntentActivitiesResult which contains resolveInfos
|
||||
*/
|
||||
public QueryIntentActivitiesResult combineFilterAndCreateQueryActivitiesResponse(
|
||||
Computer computer, Intent intent, String resolvedType, String instantAppPkgName,
|
||||
String pkgName, boolean allowDynamicSplits, long matchFlags, int userId,
|
||||
int filterCallingUid, boolean resolveForStart, List<ResolveInfo> candidates,
|
||||
List<CrossProfileDomainInfo> crossProfileCandidates, boolean areWebInstantAppsDisabled,
|
||||
boolean addInstant, boolean sortResult,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
|
||||
if (shouldSkipCurrentProfile(computer, intent, resolvedType, userId)) {
|
||||
/*
|
||||
if current profile is skipped return results from cross profile after filtering
|
||||
ephemeral activities.
|
||||
*/
|
||||
candidates = resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates);
|
||||
|
||||
return new QueryIntentActivitiesResult(computer.applyPostResolutionFilter(candidates,
|
||||
instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart,
|
||||
userId, intent));
|
||||
}
|
||||
|
||||
if (pkgName == null && intent.hasWebURI()) {
|
||||
// If instant apps are not allowed and there is result only from current or cross
|
||||
// profile return it
|
||||
if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty())
|
||||
|| (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) {
|
||||
candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
|
||||
return new QueryIntentActivitiesResult(computer.applyPostResolutionFilter(
|
||||
candidates, instantAppPkgName, allowDynamicSplits, filterCallingUid,
|
||||
resolveForStart, userId, intent));
|
||||
}
|
||||
/*
|
||||
if there are multiple results from current and cross profile, combining and filtering
|
||||
results based on domain priority.
|
||||
*/
|
||||
candidates = filterCandidatesWithDomainPreferredActivitiesLPr(computer, intent,
|
||||
matchFlags, candidates, crossProfileCandidates, userId,
|
||||
areWebInstantAppsDisabled, pkgSettingFunction);
|
||||
} else {
|
||||
candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
|
||||
}
|
||||
|
||||
return new QueryIntentActivitiesResult(sortResult, addInstant, candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
* It filters and combines results from current and cross profile based on domain priority.
|
||||
* @param computer {@link Computer} instance
|
||||
* @param intent request
|
||||
* @param matchFlags flags for intent request
|
||||
* @param candidates resolveInfos from current profile
|
||||
* @param crossProfileCandidates crossProfileDomainInfos from cross profile, it have ResolveInfo
|
||||
* @param userId user id of source user
|
||||
* @param areWebInstantAppsDisabled true if web instant apps are disabled
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of ResolveInfo
|
||||
*/
|
||||
private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Computer computer,
|
||||
Intent intent, long matchFlags, List<ResolveInfo> candidates,
|
||||
List<CrossProfileDomainInfo> crossProfileCandidates, int userId,
|
||||
boolean areWebInstantAppsDisabled,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0;
|
||||
|
||||
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, "Filtering results with preferred activities. Candidates count: "
|
||||
+ candidates.size());
|
||||
}
|
||||
|
||||
final List<ResolveInfo> result =
|
||||
filterCandidatesWithDomainPreferredActivitiesLPrBody(computer, intent, matchFlags,
|
||||
candidates, crossProfileCandidates, userId, areWebInstantAppsDisabled,
|
||||
debug, pkgSettingFunction);
|
||||
|
||||
if (DEBUG_PREFERRED || DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, "Filtered results with preferred activities. New candidates count: "
|
||||
+ result.size());
|
||||
for (ResolveInfo info : result) {
|
||||
Slog.v(TAG, " + " + info.activityInfo);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters candidates satisfying domain criteria.
|
||||
* @param computer {@link Computer} instance
|
||||
* @param intent request
|
||||
* @param matchFlags flags for intent request
|
||||
* @param candidates resolveInfos from current profile
|
||||
* @param crossProfileCandidates crossProfileDomainInfos from cross profile, it have ResolveInfo
|
||||
* @param userId user id of source user
|
||||
* @param areWebInstantAppsDisabled true if web instant apps are disabled
|
||||
* @param debug true if resolution logs needed to be printed
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of resolve infos
|
||||
*/
|
||||
private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody(
|
||||
Computer computer, Intent intent, long matchFlags, List<ResolveInfo> candidates,
|
||||
List<CrossProfileDomainInfo> crossProfileCandidates, int userId,
|
||||
boolean areWebInstantAppsDisabled, boolean debug,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
final ArrayList<ResolveInfo> result = new ArrayList<>();
|
||||
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
|
||||
final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
|
||||
|
||||
// Blocking instant apps is usually done in applyPostResolutionFilter, but since
|
||||
// domain verification can resolve to a single result, which can be an instant app,
|
||||
// it will then be filtered to an empty list in that method. Instead, do blocking
|
||||
// here so that instant apps can be ignored for approval filtering and a lower
|
||||
// priority result chosen instead.
|
||||
final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled;
|
||||
|
||||
final int count = candidates.size();
|
||||
// First, try to use approved apps.
|
||||
for (int n = 0; n < count; n++) {
|
||||
ResolveInfo info = candidates.get(n);
|
||||
if (blockInstant && (info.isInstantAppAvailable
|
||||
|| computer.isInstantAppInternal(info.activityInfo.packageName, userId,
|
||||
Process.SYSTEM_UID))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to the special match all list (Browser use case)
|
||||
if (info.handleAllWebDataURI) {
|
||||
matchAllList.add(info);
|
||||
} else {
|
||||
undefinedList.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
// We'll want to include browser possibilities in a few cases
|
||||
boolean includeBrowser = false;
|
||||
|
||||
/**
|
||||
* Grouping CrossProfileDomainInfo based on target user
|
||||
*/
|
||||
SparseArray<List<CrossProfileDomainInfo>> categorizeResolveInfoByTargetUser =
|
||||
new SparseArray<>();
|
||||
if (crossProfileCandidates != null && !crossProfileCandidates.isEmpty()) {
|
||||
for (int index = 0; index < crossProfileCandidates.size(); index++) {
|
||||
CrossProfileDomainInfo crossProfileDomainInfo = crossProfileCandidates.get(index);
|
||||
if (!categorizeResolveInfoByTargetUser
|
||||
.contains(crossProfileDomainInfo.mTargetUserId)) {
|
||||
categorizeResolveInfoByTargetUser.put(crossProfileDomainInfo.mTargetUserId,
|
||||
new ArrayList<>());
|
||||
}
|
||||
categorizeResolveInfoByTargetUser.get(crossProfileDomainInfo.mTargetUserId)
|
||||
.add(crossProfileDomainInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) {
|
||||
result.addAll(undefinedList);
|
||||
|
||||
// calling cross profile strategy to filter corresponding results
|
||||
result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
|
||||
intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
|
||||
DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE));
|
||||
includeBrowser = true;
|
||||
} else {
|
||||
Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
|
||||
.filterToApprovedApp(intent, undefinedList, userId, pkgSettingFunction);
|
||||
List<ResolveInfo> approvedInfos = infosAndLevel.first;
|
||||
Integer highestApproval = infosAndLevel.second;
|
||||
|
||||
// If no apps are approved for the domain, resolve only to browsers
|
||||
if (approvedInfos.isEmpty()) {
|
||||
includeBrowser = true;
|
||||
// calling cross profile strategy to filter corresponding results
|
||||
result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
|
||||
intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
|
||||
DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE));
|
||||
} else {
|
||||
result.addAll(approvedInfos);
|
||||
|
||||
// If the other profile has an app that's higher approval, add it
|
||||
// calling cross profile strategy to filter corresponding results
|
||||
result.addAll(filterCrossProfileCandidatesWithDomainPreferredActivities(computer,
|
||||
intent, matchFlags, categorizeResolveInfoByTargetUser, userId,
|
||||
highestApproval));
|
||||
}
|
||||
}
|
||||
|
||||
if (includeBrowser) {
|
||||
// Also add browsers (all of them or only the default one)
|
||||
if (DEBUG_DOMAIN_VERIFICATION) {
|
||||
Slog.v(TAG, " ...including browsers in candidate set");
|
||||
}
|
||||
if ((matchFlags & MATCH_ALL) != 0) {
|
||||
result.addAll(matchAllList);
|
||||
} else {
|
||||
// Browser/generic handling case. If there's a default browser, go straight
|
||||
// to that (but only if there is no other higher-priority match).
|
||||
final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(
|
||||
userId);
|
||||
int maxMatchPrio = 0;
|
||||
ResolveInfo defaultBrowserMatch = null;
|
||||
final int numCandidates = matchAllList.size();
|
||||
for (int n = 0; n < numCandidates; n++) {
|
||||
ResolveInfo info = matchAllList.get(n);
|
||||
// track the highest overall match priority...
|
||||
if (info.priority > maxMatchPrio) {
|
||||
maxMatchPrio = info.priority;
|
||||
}
|
||||
// ...and the highest-priority default browser match
|
||||
if (info.activityInfo.packageName.equals(defaultBrowserPackageName)) {
|
||||
if (defaultBrowserMatch == null
|
||||
|| (defaultBrowserMatch.priority < info.priority)) {
|
||||
if (debug) {
|
||||
Slog.v(TAG, "Considering default browser match " + info);
|
||||
}
|
||||
defaultBrowserMatch = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defaultBrowserMatch != null
|
||||
&& defaultBrowserMatch.priority >= maxMatchPrio
|
||||
&& !TextUtils.isEmpty(defaultBrowserPackageName)) {
|
||||
if (debug) {
|
||||
Slog.v(TAG, "Default browser match " + defaultBrowserMatch);
|
||||
}
|
||||
result.add(defaultBrowserMatch);
|
||||
} else {
|
||||
result.addAll(matchAllList);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is nothing selected, add all candidates
|
||||
if (result.size() == 0) {
|
||||
result.addAll(candidates);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter cross profile results by calling their respective strategy
|
||||
* @param computer {@link Computer} instance
|
||||
* @param intent request
|
||||
* @param flags for intent request
|
||||
* @param categorizeResolveInfoByTargetUser group of targetuser and its corresponding
|
||||
* CrossProfileDomainInfos
|
||||
* @param sourceUserId user id for intent
|
||||
* @param highestApprovalLevel domain approval level
|
||||
* @return list of ResolveInfos
|
||||
*/
|
||||
private List<ResolveInfo> filterCrossProfileCandidatesWithDomainPreferredActivities(
|
||||
Computer computer, Intent intent, long flags, SparseArray<List<CrossProfileDomainInfo>>
|
||||
categorizeResolveInfoByTargetUser, int sourceUserId, int highestApprovalLevel) {
|
||||
|
||||
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
|
||||
UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
|
||||
UserInfo sourceUserInfo = umInternal.getUserInfo(sourceUserId);
|
||||
|
||||
for (int index = 0; index < categorizeResolveInfoByTargetUser.size(); index++) {
|
||||
|
||||
// if resolve info does not target user or has default value, add results as they are.
|
||||
if (categorizeResolveInfoByTargetUser.keyAt(index) == -2) {
|
||||
crossProfileDomainInfos.addAll(categorizeResolveInfoByTargetUser.valueAt(index));
|
||||
} else {
|
||||
// finding cross profile strategy based on source and target user
|
||||
CrossProfileResolver crossProfileIntentResolver =
|
||||
chooseCrossProfileResolver(computer, sourceUserInfo, umInternal
|
||||
.getUserInfo(categorizeResolveInfoByTargetUser.keyAt(index)));
|
||||
// if strategy is available call it and add its filtered results
|
||||
if (crossProfileIntentResolver != null) {
|
||||
crossProfileDomainInfos.addAll(crossProfileIntentResolver
|
||||
.filterResolveInfoWithDomainPreferredActivity(intent,
|
||||
categorizeResolveInfoByTargetUser.valueAt(index),
|
||||
flags, sourceUserId, categorizeResolveInfoByTargetUser
|
||||
.keyAt(index), highestApprovalLevel));
|
||||
} else {
|
||||
// if strategy is not available call it, add the results
|
||||
crossProfileDomainInfos.addAll(categorizeResolveInfoByTargetUser
|
||||
.valueAt(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolveInfoFromCrossProfileDomainInfo(crossProfileDomainInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract ResolveInfo from CrossProfileDomainInfo
|
||||
* @param crossProfileDomainInfos cross profile results
|
||||
* @return list of ResolveInfo
|
||||
*/
|
||||
private List<ResolveInfo> resolveInfoFromCrossProfileDomainInfo(List<CrossProfileDomainInfo>
|
||||
crossProfileDomainInfos) {
|
||||
List<ResolveInfo> resolveInfoList = new ArrayList<>();
|
||||
|
||||
for (int infoIndex = 0; infoIndex < crossProfileDomainInfos.size(); infoIndex++) {
|
||||
resolveInfoList.add(crossProfileDomainInfos.get(infoIndex).mResolveInfo);
|
||||
}
|
||||
|
||||
return resolveInfoList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.server.pm.pkg.PackageStateInternal;
|
||||
import com.android.server.pm.resolution.ComponentResolverApi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Abstract Class act as base class for Cross Profile strategy.
|
||||
* This will be used by {@link CrossProfileIntentResolverEngine} to resolve intent across profile.
|
||||
*/
|
||||
public abstract class CrossProfileResolver {
|
||||
|
||||
protected ComponentResolverApi mComponentResolver;
|
||||
protected UserManagerService mUserManager;
|
||||
|
||||
public CrossProfileResolver(ComponentResolverApi componentResolver,
|
||||
UserManagerService userManager) {
|
||||
mComponentResolver = componentResolver;
|
||||
mUserManager = userManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method would be overridden by concrete implementation. This method should define how to
|
||||
* resolve given intent request in target profile.
|
||||
* @param computer ComputerEngine instance that would be needed by ComponentResolverApi
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param userId source/initiating user
|
||||
* @param targetUserId target user id
|
||||
* @param flags of intent request
|
||||
* @param pkgName package name if defined.
|
||||
* @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
|
||||
* targeting the targetUserId
|
||||
* @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
|
||||
* resolveInfo in their profile.
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of {@link CrossProfileDomainInfo}
|
||||
*/
|
||||
public abstract List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
|
||||
String resolvedType, int userId, int targetUserId, long flags,
|
||||
String pkgName, List<CrossProfileIntentFilter> matchingFilters,
|
||||
boolean hasNonNegativePriorityResult,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction);
|
||||
|
||||
/**
|
||||
* Filters the CrossProfileDomainInfos, the filtering technique would be defined by concrete
|
||||
* implementation class
|
||||
* @param intent request
|
||||
* @param crossProfileDomainInfos resolved in target user
|
||||
* @param flags for intent resolution
|
||||
* @param sourceUserId source user
|
||||
* @param targetUserId target user
|
||||
* @param highestApprovalLevel highest level of domain approval
|
||||
* @return filtered list of {@link CrossProfileDomainInfo}
|
||||
*/
|
||||
public abstract List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(
|
||||
Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags,
|
||||
int sourceUserId, int targetUserId, int highestApprovalLevel);
|
||||
|
||||
/**
|
||||
* Checks if mentioned user is enabled
|
||||
* @param userId of requested user
|
||||
* @return true if user is enabled
|
||||
*/
|
||||
protected final boolean isUserEnabled(int userId) {
|
||||
final long callingId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
UserInfo userInfo = mUserManager.getUserInfo(userId);
|
||||
return userInfo != null && userInfo.isEnabled();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out {@link CrossProfileDomainInfo} if they are not for any user apart from system
|
||||
* user. If mentioned user is system user, then returns all responses.
|
||||
* @param crossProfileDomainInfos result from resolution
|
||||
* @param userId source user id
|
||||
* @return filtered list of {@link CrossProfileDomainInfo}
|
||||
*/
|
||||
protected final List<CrossProfileDomainInfo> filterIfNotSystemUser(
|
||||
List<CrossProfileDomainInfo> crossProfileDomainInfos, int userId) {
|
||||
if (userId == UserHandle.USER_SYSTEM) {
|
||||
return crossProfileDomainInfos;
|
||||
}
|
||||
|
||||
for (int i = CollectionUtils.size(crossProfileDomainInfos) - 1; i >= 0; i--) {
|
||||
ResolveInfo info = crossProfileDomainInfos.get(i).mResolveInfo;
|
||||
if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
|
||||
crossProfileDomainInfos.remove(i);
|
||||
}
|
||||
}
|
||||
return crossProfileDomainInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user info of parent profile is applicable
|
||||
* @param userId requested user
|
||||
* @return parent's user info, null if parent is not present
|
||||
*/
|
||||
protected final UserInfo getProfileParent(int userId) {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mUserManager.getProfileParent(userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import com.android.internal.app.IntentForwarderActivity;
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.server.pm.pkg.PackageStateInternal;
|
||||
import com.android.server.pm.resolution.ComponentResolverApi;
|
||||
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Cross profile resolver used as default strategy. Primary known use-case for this resolver is
|
||||
* work/managed profile .
|
||||
*/
|
||||
public final class DefaultCrossProfileResolver extends CrossProfileResolver {
|
||||
|
||||
private final DomainVerificationManagerInternal mDomainVerificationManager;
|
||||
|
||||
|
||||
public DefaultCrossProfileResolver(ComponentResolverApi componentResolver,
|
||||
UserManagerService userManager,
|
||||
DomainVerificationManagerInternal domainVerificationManager) {
|
||||
super(componentResolver, userManager);
|
||||
mDomainVerificationManager = domainVerificationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is Default resolution strategy primarily used by Work Profile.
|
||||
* First, it checks if we have to skip source profile and just resolve in target profile. If
|
||||
* yes, then it will return result from target profile.
|
||||
* Secondly, it find specific resolve infos in target profile
|
||||
* Thirdly, if it is web intent it finds if parent can also resolve it. The results of this
|
||||
* stage gets higher priority as compared to second stage.
|
||||
*
|
||||
* @param computer ComputerEngine instance that would be needed by ComponentResolverApi
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param userId source/initiating user
|
||||
* @param targetUserId target user id
|
||||
* @param flags of intent request
|
||||
* @param pkgName the application package name this Intent is limited to.
|
||||
* @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
|
||||
* targeting the targetUserId
|
||||
* @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
|
||||
* resolveInfo in their profile.
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return list of {@link CrossProfileDomainInfo}
|
||||
*/
|
||||
@Override
|
||||
public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
|
||||
String resolvedType, int userId, int targetUserId,
|
||||
long flags, String pkgName, List<CrossProfileIntentFilter> matchingFilters,
|
||||
boolean hasNonNegativePriorityResult,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
|
||||
List<CrossProfileDomainInfo> xpResult = new ArrayList<>();
|
||||
if (pkgName != null) return xpResult;
|
||||
CrossProfileDomainInfo skipProfileInfo = querySkipCurrentProfileIntents(computer,
|
||||
matchingFilters, intent, resolvedType, flags, userId, pkgSettingFunction);
|
||||
|
||||
if (skipProfileInfo != null) {
|
||||
xpResult.add(skipProfileInfo);
|
||||
return filterIfNotSystemUser(xpResult, userId);
|
||||
}
|
||||
|
||||
CrossProfileDomainInfo specificXpInfo = queryCrossProfileIntents(computer,
|
||||
matchingFilters, intent, resolvedType, flags, userId,
|
||||
hasNonNegativePriorityResult, pkgSettingFunction);
|
||||
|
||||
if (intent.hasWebURI()) {
|
||||
CrossProfileDomainInfo generalXpInfo = null;
|
||||
final UserInfo parent = getProfileParent(userId);
|
||||
if (parent != null) {
|
||||
generalXpInfo = computer.getCrossProfileDomainPreferredLpr(intent, resolvedType,
|
||||
flags, userId, parent.id);
|
||||
}
|
||||
CrossProfileDomainInfo prioritizedXpInfo =
|
||||
generalXpInfo != null ? generalXpInfo : specificXpInfo;
|
||||
if (prioritizedXpInfo != null) {
|
||||
xpResult.add(prioritizedXpInfo);
|
||||
}
|
||||
} else if (specificXpInfo != null) {
|
||||
xpResult.add(specificXpInfo);
|
||||
}
|
||||
|
||||
return xpResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out CrossProfileDomainInfo if it does not have higher approval level as compared to
|
||||
* given approval level
|
||||
* @param intent request
|
||||
* @param crossProfileDomainInfos resolved in target user
|
||||
* @param flags for intent resolution
|
||||
* @param sourceUserId source user
|
||||
* @param targetUserId target user
|
||||
* @param highestApprovalLevel highest level of domain approval
|
||||
* @return filtered list of CrossProfileDomainInfo
|
||||
*/
|
||||
@Override
|
||||
public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(
|
||||
Intent intent, List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags,
|
||||
int sourceUserId, int targetUserId, int highestApprovalLevel) {
|
||||
|
||||
List<CrossProfileDomainInfo> filteredCrossProfileDomainInfos = new ArrayList<>();
|
||||
|
||||
if (crossProfileDomainInfos != null && !crossProfileDomainInfos.isEmpty()) {
|
||||
for (int index = 0; index < crossProfileDomainInfos.size(); index++) {
|
||||
CrossProfileDomainInfo crossProfileDomainInfo = crossProfileDomainInfos.get(index);
|
||||
if (crossProfileDomainInfo.mHighestApprovalLevel > highestApprovalLevel) {
|
||||
filteredCrossProfileDomainInfos.add(crossProfileDomainInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCrossProfileDomainInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* If current/source profile needs to be skipped, returns CrossProfileDomainInfo from target
|
||||
* profile. If any of the matchingFilters have flag {@link PackageManager#SKIP_CURRENT_PROFILE}
|
||||
* set that would signify that current profile needs to be skipped.
|
||||
* @param computer ComputerEngine instance that would be needed by ComponentResolverApi
|
||||
* @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
|
||||
* targeting the targetUserId
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param flags for intent resolution
|
||||
* @param sourceUserId source user
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return CrossProfileDomainInfo if current profile needs to be skipped, else null
|
||||
*/
|
||||
@Nullable
|
||||
private CrossProfileDomainInfo querySkipCurrentProfileIntents(Computer computer,
|
||||
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
|
||||
long flags, int sourceUserId,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
if (matchingFilters != null) {
|
||||
int size = matchingFilters.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
CrossProfileIntentFilter filter = matchingFilters.get(i);
|
||||
if ((filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0) {
|
||||
// Checking if there are activities in the target user that can handle the
|
||||
// intent.
|
||||
CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter,
|
||||
intent, resolvedType, flags, sourceUserId, pkgSettingFunction);
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves and returns CrossProfileDomainInfo(ForwardingResolveInfo) from target profile if
|
||||
* current profile should be skipped when there is no result or if target profile should not
|
||||
* be skipped.
|
||||
*
|
||||
* @param computer ComputerEngine instance that would be needed by ComponentResolverApi
|
||||
* @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
|
||||
* targeting the targetUserId
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param flags for intent resolution
|
||||
* @param sourceUserId source user
|
||||
* @param matchInCurrentProfile true if current/source profile have some non-negative
|
||||
* resolveInfo
|
||||
* @param pkgSettingFunction function to find PackageStateInternal for given package
|
||||
* @return CrossProfileDomainInfo returns forwarding intent resolver in CrossProfileDomainInfo.
|
||||
* It returns null if there are no matching filters or no valid/active activity available
|
||||
*/
|
||||
@Nullable
|
||||
private CrossProfileDomainInfo queryCrossProfileIntents(Computer computer,
|
||||
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
|
||||
long flags, int sourceUserId, boolean matchInCurrentProfile,
|
||||
Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
if (matchingFilters == null) {
|
||||
return null;
|
||||
}
|
||||
// Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
|
||||
// match the same intent. For performance reasons, it is better not to
|
||||
// run queryIntent twice for the same userId
|
||||
SparseBooleanArray alreadyTriedUserIds = new SparseBooleanArray();
|
||||
|
||||
CrossProfileDomainInfo resultInfo = null;
|
||||
|
||||
int size = matchingFilters.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
CrossProfileIntentFilter filter = matchingFilters.get(i);
|
||||
int targetUserId = filter.getTargetUserId();
|
||||
boolean skipCurrentProfile =
|
||||
(filter.getFlags() & PackageManager.SKIP_CURRENT_PROFILE) != 0;
|
||||
boolean skipCurrentProfileIfNoMatchFound =
|
||||
(filter.getFlags() & PackageManager.ONLY_IF_NO_MATCH_FOUND) != 0;
|
||||
if (!skipCurrentProfile && !alreadyTriedUserIds.get(targetUserId)
|
||||
&& (!skipCurrentProfileIfNoMatchFound || !matchInCurrentProfile)) {
|
||||
// Checking if there are activities in the target user that can handle the
|
||||
// intent.
|
||||
CrossProfileDomainInfo info = createForwardingResolveInfo(computer, filter, intent,
|
||||
resolvedType, flags, sourceUserId, pkgSettingFunction);
|
||||
if (info != null) {
|
||||
resultInfo = info;
|
||||
break;
|
||||
}
|
||||
alreadyTriedUserIds.put(targetUserId, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResolveInfo forwardingResolveInfo = resultInfo.mResolveInfo;
|
||||
if (!isUserEnabled(forwardingResolveInfo.targetUserId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<CrossProfileDomainInfo> filteredResult =
|
||||
filterIfNotSystemUser(Collections.singletonList(resultInfo), sourceUserId);
|
||||
if (filteredResult.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Forwarding Resolve Info, used when we have to signify that target profile's
|
||||
* resolveInfo should be considered without providing list of resolve infos.
|
||||
* @param computer ComputerEngine instance that would be needed by ComponentResolverApi
|
||||
* @param filter {@link CrossProfileIntentFilter} configured for source user,
|
||||
* targeting the targetUserId
|
||||
* @param intent request
|
||||
* @param resolvedType the MIME data type of intent request
|
||||
* @param flags for intent resolution
|
||||
* @param sourceUserId source user
|
||||
* @return CrossProfileDomainInfo whose ResolveInfo is forwarding. It would be resolved by
|
||||
* {@link IntentForwarderActivity}. It returns null if there are no valid/active activities
|
||||
*/
|
||||
@Nullable
|
||||
protected CrossProfileDomainInfo createForwardingResolveInfo(Computer computer,
|
||||
@NonNull CrossProfileIntentFilter filter, @NonNull Intent intent,
|
||||
@Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
|
||||
int sourceUserId, @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
|
||||
int targetUserId = filter.getTargetUserId();
|
||||
if (!isUserEnabled(targetUserId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent,
|
||||
resolvedType, flags, targetUserId);
|
||||
if (CollectionUtils.isEmpty(resultTargetUser)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResolveInfo forwardingInfo = null;
|
||||
for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
|
||||
ResolveInfo targetUserResolveInfo = resultTargetUser.get(i);
|
||||
if ((targetUserResolveInfo.activityInfo.applicationInfo.flags
|
||||
& ApplicationInfo.FLAG_SUSPENDED) == 0) {
|
||||
forwardingInfo = computer.createForwardingResolveInfoUnchecked(filter, sourceUserId,
|
||||
targetUserId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardingInfo == null) {
|
||||
// If all the matches in the target profile are suspended, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
|
||||
|
||||
int size = resultTargetUser.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ResolveInfo riTargetUser = resultTargetUser.get(i);
|
||||
if (riTargetUser.handleAllWebDataURI) {
|
||||
continue;
|
||||
}
|
||||
String packageName = riTargetUser.activityInfo.packageName;
|
||||
PackageStateInternal ps = pkgSettingFunction.apply(packageName);
|
||||
if (ps == null) {
|
||||
continue;
|
||||
}
|
||||
highestApprovalLevel = Math.max(highestApprovalLevel, mDomainVerificationManager
|
||||
.approvalLevelForDomain(ps, intent, flags, targetUserId));
|
||||
}
|
||||
|
||||
return new CrossProfileDomainInfo(forwardingInfo, highestApprovalLevel, targetUserId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user