blob: 203e1de61f2f1553bf1ee4e9c49be06eab4f25a5 [file] [log] [blame]
/*
* Copyright (C) 2021 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.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IUnsafeIntentStrictModeCallback;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import com.android.internal.app.ResolverActivity;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerUtils;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.pkg.AndroidPackage;
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.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
final class ResolveIntentHelper {
@NonNull
private final Context mContext;
@NonNull
private final PlatformCompat mPlatformCompat;
@NonNull
private final UserManagerService mUserManager;
@NonNull
private final PreferredActivityHelper mPreferredActivityHelper;
@NonNull
private final DomainVerificationManagerInternal mDomainVerificationManager;
@NonNull
private final UserNeedsBadgingCache mUserNeedsBadging;
@NonNull
private final Supplier<ResolveInfo> mResolveInfoSupplier;
@NonNull
private final Supplier<ActivityInfo> mInstantAppInstallerActivitySupplier;
@NonNull
private final Handler mHandler;
ResolveIntentHelper(@NonNull Context context,
@NonNull PreferredActivityHelper preferredActivityHelper,
@NonNull PlatformCompat platformCompat, @NonNull UserManagerService userManager,
@NonNull DomainVerificationManagerInternal domainVerificationManager,
@NonNull UserNeedsBadgingCache userNeedsBadgingCache,
@NonNull Supplier<ResolveInfo> resolveInfoSupplier,
@NonNull Supplier<ActivityInfo> instantAppInstallerActivitySupplier,
@NonNull Handler handler) {
mContext = context;
mPreferredActivityHelper = preferredActivityHelper;
mPlatformCompat = platformCompat;
mUserManager = userManager;
mDomainVerificationManager = domainVerificationManager;
mUserNeedsBadging = userNeedsBadgingCache;
mResolveInfoSupplier = resolveInfoSupplier;
mInstantAppInstallerActivitySupplier = instantAppInstallerActivitySupplier;
mHandler = handler;
}
private static void filterNonExportedComponents(Intent intent, int filterCallingUid,
int callingPid, List<ResolveInfo> query, PlatformCompat platformCompat,
String resolvedType, Computer computer, Handler handler) {
if (query == null
|| intent.getPackage() != null
|| intent.getComponent() != null
|| ActivityManager.canAccessUnexportedComponents(filterCallingUid)) {
return;
}
AndroidPackage caller = computer.getPackage(filterCallingUid);
String callerPackage = caller == null ? "Not specified" : caller.getPackageName();
ActivityManagerInternal activityManagerInternal = LocalServices
.getService(ActivityManagerInternal.class);
final IUnsafeIntentStrictModeCallback callback = activityManagerInternal
.getRegisteredStrictModeCallback(callingPid);
for (int i = query.size() - 1; i >= 0; i--) {
if (!query.get(i).getComponentInfo().exported) {
boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
filterCallingUid);
ActivityManagerUtils.logUnsafeIntentEvent(
UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
filterCallingUid, intent, resolvedType, hasToBeExportedToMatch);
if (callback != null) {
handler.post(() -> {
try {
callback.onImplicitIntentMatchedInternalComponent(intent.cloneFilter());
} catch (RemoteException e) {
activityManagerInternal.unregisterStrictModeCallback(callingPid);
}
});
}
if (!hasToBeExportedToMatch) {
return;
}
query.remove(i);
}
}
}
/**
* Normally instant apps can only be resolved when they're visible to the caller.
* However, if {@code resolveForStart} is {@code true}, all instant apps are visible
* since we need to allow the system to start any installed application.
*/
public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
boolean resolveForStart, int filterCallingUid) {
return resolveIntentInternal(computer, intent, resolvedType, flags,
privateResolveFlags, userId, resolveForStart, filterCallingUid, false, 0);
}
/**
* Normally instant apps can only be resolved when they're visible to the caller.
* However, if {@code resolveForStart} is {@code true}, all instant apps are visible
* since we need to allow the system to start any installed application.
* Allows picking exported components only.
*/
public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
boolean resolveForStart, int filterCallingUid, boolean exportedComponentsOnly,
int callingPid) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
if (!mUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = computer.updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
resolvedType, flags));
computer.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "resolve intent");
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
final List<ResolveInfo> query = computer.queryIntentActivitiesInternal(intent,
resolvedType, flags, privateResolveFlags, filterCallingUid, userId,
resolveForStart, true /*allowDynamicSplits*/);
if (exportedComponentsOnly) {
filterNonExportedComponents(intent, filterCallingUid, callingPid, query,
mPlatformCompat, resolvedType, computer, mHandler);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final boolean queryMayBeFiltered =
UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
&& !resolveForStart;
final ResolveInfo bestChoice = chooseBestActivity(computer, intent, resolvedType, flags,
privateResolveFlags, query, userId, queryMayBeFiltered);
final boolean nonBrowserOnly =
(privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
return null;
}
return bestChoice;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private ResolveInfo chooseBestActivity(Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
List<ResolveInfo> query, int userId, boolean queryMayBeFiltered) {
if (query != null) {
final int n = query.size();
if (n == 1) {
return query.get(0);
} else if (n > 1) {
final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
// If there is more than one activity with the same priority,
// then let the user decide between them.
ResolveInfo r0 = query.get(0);
ResolveInfo r1 = query.get(1);
if (DEBUG_INTENT_MATCHING || debug) {
Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+ r1.activityInfo.name + "=" + r1.priority);
}
// If the first activity has a higher priority, or a different
// default, then it is always desirable to pick it.
if (r0.priority != r1.priority
|| r0.preferredOrder != r1.preferredOrder
|| r0.isDefault != r1.isDefault) {
return query.get(0);
}
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = mPreferredActivityHelper.findPreferredActivityNotLocked(computer,
intent, resolvedType, flags, query, true, false, debug,
userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}
int browserCount = 0;
for (int i = 0; i < n; i++) {
ri = query.get(i);
if (ri.handleAllWebDataURI) {
browserCount++;
}
// If we have an ephemeral app, use it
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
final PackageStateInternal ps =
computer.getPackageStateInternal(packageName);
if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
mDomainVerificationManager, ps, intent, flags, userId)) {
return ri;
}
}
}
if ((privateResolveFlags
& PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
return null;
}
ri = new ResolveInfo(mResolveInfoSupplier.get());
// if all resolve options are browsers, mark the resolver's info as if it were
// also a browser.
ri.handleAllWebDataURI = browserCount == n;
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
if (ri.userHandle == null) ri.userHandle = UserHandle.of(userId);
// If all of the options come from the same package, show the application's
// label and icon instead of the generic resolver's.
// Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
// and then throw away the ResolveInfo itself, meaning that the caller loses
// the resolvePackageName. Therefore the activityInfo.labelRes above provides
// a fallback for this case; we only set the target package's resources on
// the ResolveInfo, not the ActivityInfo.
final String intentPackage = intent.getPackage();
if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
ri.resolvePackageName = intentPackage;
if (mUserNeedsBadging.get(userId)) {
ri.noResourceId = true;
} else {
ri.icon = appi.icon;
}
ri.iconResourceId = appi.icon;
ri.labelRes = appi.labelRes;
}
ri.activityInfo.applicationInfo = new ApplicationInfo(
ri.activityInfo.applicationInfo);
if (userId != 0) {
ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
}
// Make sure that the resolver is displayable in car mode
if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
return ri;
}
}
return null;
}
/**
* Return true if the given list is not empty and all of its contents have
* an activityInfo with the given package name.
*/
private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
if (ArrayUtils.isEmpty(list)) {
return false;
}
for (int i = 0, n = list.size(); i < n; i++) {
final ResolveInfo ri = list.get(i);
final ActivityInfo ai = ri != null ? ri.activityInfo : null;
if (ai == null || !packageName.equals(ai.packageName)) {
return false;
}
}
return true;
}
public IntentSender getLaunchIntentSenderForPackage(@NonNull Computer computer,
String packageName, String callingPackage, String featureId, int userId)
throws RemoteException {
Objects.requireNonNull(packageName);
final int callingUid = Binder.getCallingUid();
computer.enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "get launch intent sender for package");
final int packageUid = computer.getPackageUid(callingPackage, 0 /* flags */, userId);
if (!UserHandle.isSameApp(callingUid, packageUid)) {
throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
+ callingUid + " does not own package: " + callingPackage);
}
// Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
// Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
final ContentResolver contentResolver = mContext.getContentResolver();
String resolvedType = intentToResolve.resolveTypeIfNeeded(contentResolver);
List<ResolveInfo> ris = computer.queryIntentActivitiesInternal(intentToResolve, resolvedType,
0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
true /* resolveForStart */, false /* allowDynamicSplits */);
if (ris == null || ris.size() <= 0) {
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
resolvedType = intentToResolve.resolveTypeIfNeeded(contentResolver);
ris = computer.queryIntentActivitiesInternal(intentToResolve, resolvedType,
0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
true /* resolveForStart */, false /* allowDynamicSplits */);
}
final Intent intent = new Intent(intentToResolve);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// For the case of empty result, no component name is assigned into the intent. A
// non-launchable IntentSender which contains the failed intent is created. The
// SendIntentException is thrown if the IntentSender#sendIntent is invoked.
if (ris != null && !ris.isEmpty()) {
// am#isIntentSenderTargetedToPackage returns false if both package name and component
// name are set in the intent. Clear the package name to have the api return true and
// prevent the package existence info from side channel leaks by the api.
intent.setPackage(null);
intent.setClassName(ris.get(0).activityInfo.packageName,
ris.get(0).activityInfo.name);
}
final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
featureId, null /* token */, null /* resultWho */,
1 /* requestCode */, new Intent[]{intent},
resolvedType != null ? new String[]{resolvedType} : null,
PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
return new IntentSender(target);
}
/**
* Retrieve all receivers that can handle a broadcast of the given intent.
* @param queryingUid the results will be filtered in the context of this UID instead.
*/
@NonNull
public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int queryingUid) {
return queryIntentReceiversInternal(computer, intent, resolvedType, flags, userId,
queryingUid, false);
}
/**
* @see PackageManagerInternal#queryIntentReceivers(Intent, String, long, int, int, boolean)
*/
@NonNull
public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int filterCallingUid, boolean forSend) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
// The identity used to filter the receiver components
final int queryingUid = forSend ? Process.SYSTEM_UID : filterCallingUid;
computer.enforceCrossUserPermission(queryingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers");
final String instantAppPkgName = computer.getInstantAppPackageName(queryingUid);
flags = computer.updateFlagsForResolve(flags, userId, queryingUid,
false /*includeInstantApps*/,
computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
resolvedType, flags));
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
final ComponentResolverApi componentResolver = computer.getComponentResolver();
List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
final ActivityInfo ai = computer.getReceiverInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
// used when either 1) the calling package is normal and the activity is within
// an instant application or 2) the calling package is ephemeral and the
// activity is not visible to instant applications.
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean matchExplicitlyVisibleOnly =
(flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
final boolean isCallerInstantApp =
instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(ai.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetVisibleToInstantApp =
(ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
final boolean isTargetExplicitlyVisibleToInstantApp = isTargetVisibleToInstantApp
&& (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
final boolean isTargetHiddenFromInstantApp = !isTargetVisibleToInstantApp
|| (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
final boolean blockResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
if (!blockResolution) {
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver, list, true, intent,
resolvedType, flags, filterCallingUid);
}
}
} else {
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> result = componentResolver
.queryReceivers(computer, intent, resolvedType, flags, userId);
if (result != null) {
list = result;
}
}
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
intent, resolvedType, flags, pkg.getReceivers(), userId);
if (result != null) {
list = result;
}
}
}
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver,
list, true, originalIntent, resolvedType, flags, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
false, userId, intent);
}
public ResolveInfo resolveServiceInternal(@NonNull Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
List<ResolveInfo> query = computer.queryIntentServicesInternal(
intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
if (query != null) {
if (query.size() >= 1) {
// If there is more than one service with the same priority,
// just arbitrarily pick the first one.
return query.get(0);
}
}
return null;
}
public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
@NonNull Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags, int userId) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null) {
final List<ResolveInfo> list = new ArrayList<>(1);
final ProviderInfo pi = computer.getProviderInfo(comp, flags, userId);
if (pi != null) {
// When specifying an explicit component, we prevent the provider from being
// used when either 1) the provider is in an instant application and the
// caller is not the same instant application or 2) the calling package is an
// instant application and the provider is not visible to instant applications.
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean isCallerInstantApp =
instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(pi.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetHiddenFromInstantApp =
(pi.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
final boolean blockResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
&& computer.shouldFilterApplication(
computer.getPackageStateInternal(pi.applicationInfo.packageName,
Process.SYSTEM_UID), callingUid, userId);
if (!blockResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.providerInfo = pi;
list.add(ri);
}
}
return list;
}
final ComponentResolverApi componentResolver = computer.getComponentResolver();
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
intent, resolvedType, flags, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
return applyPostContentProviderResolutionFilter(computer, resolveInfos,
instantAppPkgName, userId, callingUid);
}
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
intent, resolvedType, flags, pkg.getProviders(), userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
return applyPostContentProviderResolutionFilter(computer, resolveInfos,
instantAppPkgName, userId, callingUid);
}
return Collections.emptyList();
}
private List<ResolveInfo> applyPostContentProviderResolutionFilter(@NonNull Computer computer,
List<ResolveInfo> resolveInfos, String instantAppPkgName,
@UserIdInt int userId, int callingUid) {
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
final ResolveInfo info = resolveInfos.get(i);
if (instantAppPkgName == null) {
PackageStateInternal resolvedSetting = computer.getPackageStateInternal(
info.providerInfo.packageName, 0);
if (!computer.shouldFilterApplication(resolvedSetting, callingUid, userId)) {
continue;
}
}
final boolean isEphemeralApp = info.providerInfo.applicationInfo.isInstantApp();
// allow providers that are defined in the provided package
if (isEphemeralApp && instantAppPkgName.equals(info.providerInfo.packageName)) {
if (info.providerInfo.splitName != null
&& !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
info.providerInfo.splitName)) {
if (mInstantAppInstallerActivitySupplier.get() == null) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
}
resolveInfos.remove(i);
continue;
}
// requested provider is defined in a split that hasn't been installed yet.
// add the installer to the resolve list
if (DEBUG_INSTANT) {
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
}
final ResolveInfo installerInfo = new ResolveInfo(
computer.getInstantAppInstallerInfo());
installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
null /*failureActivity*/,
info.providerInfo.packageName,
info.providerInfo.applicationInfo.longVersionCode,
info.providerInfo.splitName);
// add a non-generic filter
installerInfo.filter = new IntentFilter();
// load resources from the correct package
installerInfo.resolvePackageName = info.getComponentInfo().packageName;
resolveInfos.set(i, installerInfo);
}
continue;
}
// allow providers that have been explicitly exposed to instant applications
if (!isEphemeralApp && (
(info.providerInfo.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
continue;
}
resolveInfos.remove(i);
}
return resolveInfos;
}
public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(Computer computer,
ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
flags = computer.updateFlagsForResolve(flags, userId, callingUid,
false /*includeInstantApps*/,
computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
resolvedType, flags));
computer.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "query intent activity options");
final String resultsAction = intent.getAction();
final List<ResolveInfo> results = computer.queryIntentActivitiesInternal(intent,
resolvedType, flags | PackageManager.GET_RESOLVED_FILTER, userId);
if (DEBUG_INTENT_MATCHING) {
Log.v(TAG, "Query " + intent + ": " + results);
}
int specificsPos = 0;
int N;
// todo: note that the algorithm used here is O(N^2). This
// isn't a problem in our current environment, but if we start running
// into situations where we have more than 5 or 10 matches then this
// should probably be changed to something smarter...
// First we go through and resolve each of the specific items
// that were supplied, taking care of removing any corresponding
// duplicate items in the generic resolve list.
if (specifics != null) {
for (int i = 0; i < specifics.length; i++) {
final Intent sintent = specifics[i];
if (sintent == null) {
continue;
}
if (DEBUG_INTENT_MATCHING) {
Log.v(TAG, "Specific #" + i + ": " + sintent);
}
String action = sintent.getAction();
if (resultsAction != null && resultsAction.equals(action)) {
// If this action was explicitly requested, then don't
// remove things that have it.
action = null;
}
ResolveInfo ri = null;
ActivityInfo ai = null;
ComponentName comp = sintent.getComponent();
if (comp == null) {
ri = resolveIntentInternal(computer, sintent,
specificTypes != null ? specificTypes[i] : null, flags,
0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
if (ri == null) {
continue;
}
if (ri == mResolveInfoSupplier.get()) {
// ACK! Must do something better with this.
}
ai = ri.activityInfo;
comp = new ComponentName(ai.applicationInfo.packageName,
ai.name);
} else {
ai = computer.getActivityInfo(comp, flags, userId);
if (ai == null) {
continue;
}
}
// Look for any generic query activities that are duplicates
// of this specific one, and remove them from the results.
if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
N = results.size();
int j;
for (j = specificsPos; j < N; j++) {
ResolveInfo sri = results.get(j);
if ((sri.activityInfo.name.equals(comp.getClassName())
&& sri.activityInfo.applicationInfo.packageName.equals(
comp.getPackageName()))
|| (action != null && sri.filter.matchAction(action))) {
results.remove(j);
if (DEBUG_INTENT_MATCHING) {
Log.v(
TAG, "Removing duplicate item from " + j
+ " due to specific " + specificsPos);
}
if (ri == null) {
ri = sri;
}
j--;
N--;
}
}
// Add this specific item to its proper place.
if (ri == null) {
ri = new ResolveInfo();
ri.activityInfo = ai;
}
results.add(specificsPos, ri);
ri.specificIndex = i;
specificsPos++;
}
}
// Now we go through the remaining generic results and remove any
// duplicate actions that are found here.
N = results.size();
for (int i = specificsPos; i < N - 1; i++) {
final ResolveInfo rii = results.get(i);
if (rii.filter == null) {
continue;
}
// Iterate over all of the actions of this result's intent
// filter... typically this should be just one.
final Iterator<String> it = rii.filter.actionsIterator();
if (it == null) {
continue;
}
while (it.hasNext()) {
final String action = it.next();
if (resultsAction != null && resultsAction.equals(action)) {
// If this action was explicitly requested, then don't
// remove things that have it.
continue;
}
for (int j = i + 1; j < N; j++) {
final ResolveInfo rij = results.get(j);
if (rij.filter != null && rij.filter.hasAction(action)) {
results.remove(j);
if (DEBUG_INTENT_MATCHING) {
Log.v(
TAG, "Removing duplicate item from " + j
+ " due to action " + action + " at " + i);
}
j--;
N--;
}
}
}
// If the caller didn't request filter information, drop it now
// so we don't have to marshall/unmarshall it.
if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
rii.filter = null;
}
}
// Filter out the caller activity if so requested.
if (caller != null) {
N = results.size();
for (int i = 0; i < N; i++) {
ActivityInfo ainfo = results.get(i).activityInfo;
if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
&& caller.getClassName().equals(ainfo.name)) {
results.remove(i);
break;
}
}
}
// If the caller didn't request filter information,
// drop them now so we don't have to
// marshall/unmarshall it.
if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
N = results.size();
for (int i = 0; i < N; i++) {
results.get(i).filter = null;
}
}
if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
return results;
}
}