Merge changes I88940b68,I7cd85dff

* changes:
  Move logics of suspending packages out of package manager service
  Remove unused field ProtectedPackages in the ComputerEngine
This commit is contained in:
Rhed Jao
2022-01-21 03:12:17 +00:00
committed by Android (Google) Code Review
8 changed files with 1169 additions and 610 deletions

View File

@@ -70,6 +70,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP
PACKAGE_SYSTEM,
PACKAGE_SETUP_WIZARD,
PACKAGE_INSTALLER,
PACKAGE_UNINSTALLER,
PACKAGE_VERIFIER,
PACKAGE_BROWSER,
PACKAGE_SYSTEM_TEXT_CLASSIFIER,
@@ -89,20 +90,21 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP
public static final int PACKAGE_SYSTEM = 0;
public static final int PACKAGE_SETUP_WIZARD = 1;
public static final int PACKAGE_INSTALLER = 2;
public static final int PACKAGE_VERIFIER = 3;
public static final int PACKAGE_BROWSER = 4;
public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
public static final int PACKAGE_PERMISSION_CONTROLLER = 6;
public static final int PACKAGE_WELLBEING = 7;
public static final int PACKAGE_DOCUMENTER = 8;
public static final int PACKAGE_CONFIGURATOR = 9;
public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
public static final int PACKAGE_APP_PREDICTOR = 11;
public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 12;
public static final int PACKAGE_WIFI = 13;
public static final int PACKAGE_COMPANION = 14;
public static final int PACKAGE_RETAIL_DEMO = 15;
public static final int PACKAGE_RECENTS = 16;
public static final int PACKAGE_UNINSTALLER = 3;
public static final int PACKAGE_VERIFIER = 4;
public static final int PACKAGE_BROWSER = 5;
public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6;
public static final int PACKAGE_PERMISSION_CONTROLLER = 7;
public static final int PACKAGE_WELLBEING = 8;
public static final int PACKAGE_DOCUMENTER = 9;
public static final int PACKAGE_CONFIGURATOR = 10;
public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 11;
public static final int PACKAGE_APP_PREDICTOR = 12;
public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 13;
public static final int PACKAGE_WIFI = 14;
public static final int PACKAGE_COMPANION = 15;
public static final int PACKAGE_RETAIL_DEMO = 16;
public static final int PACKAGE_RECENTS = 17;
// Integer value of the last known package ID. Increases as new ID is added to KnownPackage.
// Please note the numbers should be continuous.
public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS;
@@ -1141,6 +1143,8 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP
return "Setup Wizard";
case PACKAGE_INSTALLER:
return "Installer";
case PACKAGE_UNINSTALLER:
return "Uninstaller";
case PACKAGE_VERIFIER:
return "Verifier";
case PACKAGE_BROWSER:

View File

@@ -350,7 +350,6 @@ public class ComputerEngine implements Computer {
private final CompilerStats mCompilerStats;
private final BackgroundDexOptService mBackgroundDexOptService;
private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
private final ProtectedPackages mProtectedPackages;
// PackageManagerService attributes that are primitives are referenced through the
// pms object directly. Primitives are the only attributes so referenced.
@@ -402,7 +401,6 @@ public class ComputerEngine implements Computer {
mCompilerStats = args.service.mCompilerStats;
mBackgroundDexOptService = args.service.mBackgroundDexOptService;
mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
mProtectedPackages = args.service.mProtectedPackages;
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.

View File

@@ -234,8 +234,6 @@ import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
@@ -291,7 +289,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Keep track of all those APKs everywhere.
@@ -970,6 +967,7 @@ public class PackageManagerService extends IPackageManager.Stub
private final PreferredActivityHelper mPreferredActivityHelper;
private final ResolveIntentHelper mResolveIntentHelper;
private final DexOptHelper mDexOptHelper;
private final SuspendPackageHelper mSuspendPackageHelper;
/**
* Invalidate the package info cache, which includes updating the cached computer.
@@ -1705,6 +1703,7 @@ public class PackageManagerService extends IPackageManager.Stub
mPreferredActivityHelper = testParams.preferredActivityHelper;
mResolveIntentHelper = testParams.resolveIntentHelper;
mDexOptHelper = testParams.dexOptHelper;
mSuspendPackageHelper = testParams.suspendPackageHelper;
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
invalidatePackageInfoCache();
@@ -1851,6 +1850,8 @@ public class PackageManagerService extends IPackageManager.Stub
mPreferredActivityHelper = new PreferredActivityHelper(this);
mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
mDexOptHelper = new DexOptHelper(this);
mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
mProtectedPackages);
synchronized (mLock) {
// Create the computer as soon as the state objects have been installed. The
@@ -2682,7 +2683,7 @@ public class PackageManagerService extends IPackageManager.Stub
return mComputer.getPackageUid(packageName, flags, userId);
}
private int getPackageUidInternal(String packageName,
int getPackageUidInternal(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid);
}
@@ -4294,51 +4295,6 @@ public class PackageManagerService extends IPackageManager.Stub
info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) {
final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
final int[] userIds = new int[] {userId};
// Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
// allow lists are the same.
for (int i = 0; i < pkgList.length; i++) {
final String pkgName = pkgList[i];
final int uid = uidList[i];
SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
getPackageStateInternal(pkgName, Process.SYSTEM_UID),
userIds, getPackageStates());
if (allowList == null) {
allowList = new SparseArray<>(0);
}
boolean merged = false;
for (int j = 0; j < allowListsToSend.size(); j++) {
if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
pkgsToSend.get(j).add(pkgName);
uidsToSend.get(j).add(uid);
merged = true;
break;
}
}
if (!merged) {
pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
uidsToSend.add(IntArray.wrap(new int[] {uid}));
allowListsToSend.add(allowList);
}
}
for (int i = 0; i < pkgsToSend.size(); i++) {
final Bundle extras = new Bundle(3);
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
? null : allowListsToSend.get(i);
sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null,
null, userIds, null, allowList, null);
}
}
/**
* Returns true if application is not found or there was an error. Otherwise it returns
* the hidden state of the package for the given user.
@@ -4381,7 +4337,8 @@ public class PackageManagerService extends IPackageManager.Stub
+ userId);
}
Objects.requireNonNull(packageNames, "packageNames cannot be null");
if (restrictionFlags != 0 && !isSuspendAllowedForUser(userId)) {
if (restrictionFlags != 0
&& !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) {
Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId);
return packageNames;
}
@@ -4389,8 +4346,9 @@ public class PackageManagerService extends IPackageManager.Stub
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canRestrict = (restrictionFlags != 0) ? canSuspendPackageForUserInternal(
packageNames, userId) : null;
final boolean[] canRestrict = (restrictionFlags != 0)
? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid)
: null;
for (int i = 0; i < packageNames.length; i++) {
final String packageName = packageNames[i];
@@ -4469,84 +4427,8 @@ public class PackageManagerService extends IPackageManager.Stub
final int callingUid = Binder.getCallingUid();
enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId,
"setPackagesSuspendedAsUser");
if (ArrayUtils.isEmpty(packageNames)) {
return packageNames;
}
if (suspended && !isSuspendAllowedForUser(userId)) {
Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
return packageNames;
}
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames,
userId) : null;
for (int i = 0; i < packageNames.length; i++) {
final String packageName = packageNames[i];
if (callingPackage.equals(packageName)) {
Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+ (suspended ? "" : "un") + "suspend itself. Ignoring");
unactionedPackages.add(packageName);
continue;
}
final PackageSetting pkgSetting;
synchronized (mLock) {
pkgSetting = mSettings.getPackageLPr(packageName);
if (pkgSetting == null
|| shouldFilterApplication(pkgSetting, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageName
+ ". Skipping suspending/un-suspending.");
unactionedPackages.add(packageName);
continue;
}
}
if (canSuspend != null && !canSuspend[i]) {
unactionedPackages.add(packageName);
continue;
}
final boolean packageUnsuspended;
final boolean packageModified;
synchronized (mLock) {
if (suspended) {
packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
dialogInfo, appExtras, launcherExtras, userId);
} else {
packageModified = pkgSetting.removeSuspension(callingPackage, userId);
}
packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
}
if (suspended || packageUnsuspended) {
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
}
if (packageModified) {
modifiedPackagesList.add(packageName);
modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
}
}
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
sendPackagesSuspendedForUser(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!modifiedPackagesList.isEmpty()) {
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
}
return unactionedPackages.toArray(new String[0]);
return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras,
launcherExtras, dialogInfo, callingPackage, userId, callingUid);
}
@Override
@@ -4556,56 +4438,8 @@ public class PackageManagerService extends IPackageManager.Stub
throw new SecurityException("Calling package " + packageName
+ " does not belong to calling uid " + callingUid);
}
return getSuspendedPackageAppExtrasInternal(packageName, userId);
}
private Bundle getSuspendedPackageAppExtrasInternal(String packageName, int userId) {
final PackageStateInternal ps = getPackageStateInternal(packageName);
if (ps == null) {
return null;
}
final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId);
final Bundle allExtras = new Bundle();
if (pus.isSuspended()) {
for (int i = 0; i < pus.getSuspendParams().size(); i++) {
final SuspendParams params = pus.getSuspendParams().valueAt(i);
if (params != null && params.getAppExtras() != null) {
allExtras.putAll(params.getAppExtras());
}
}
}
return (allExtras.size() > 0) ? allExtras : null;
}
private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
int userId) {
final String action = suspended
? Intent.ACTION_MY_PACKAGE_SUSPENDED
: Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
mHandler.post(() -> {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+ (suspended ? "" : "UN") + "SUSPENDED broadcasts");
return;
}
final int[] targetUserIds = new int[] {userId};
for (String packageName : affectedPackages) {
final Bundle appExtras = suspended
? getSuspendedPackageAppExtrasInternal(packageName, userId)
: null;
final Bundle intentExtras;
if (appExtras != null) {
intentExtras = new Bundle(1);
intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
} else {
intentExtras = null;
}
mHandler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
targetUserIds, false, null, null));
}
});
return mSuspendPackageHelper.getSuspendedPackageAppExtras(
packageName, userId, callingUid);
}
@Override
@@ -4618,50 +4452,14 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mLock) {
allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
}
removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId);
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
allPackages, suspendingPackage::equals, userId);
}
private boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
return mComputer.isSuspendingAnyPackages(suspendingPackage, userId);
}
/**
* Removes any suspensions on given packages that were added by packages that pass the given
* predicate.
*
* <p> Caller must flush package restrictions if it cares about immediate data consistency.
*
* @param packagesToChange The packages on which the suspension are to be removed.
* @param suspendingPackagePredicate A predicate identifying the suspending packages whose
* suspensions will be removed.
* @param userId The user for which the changes are taking place.
*/
private void removeSuspensionsBySuspendingPackage(String[] packagesToChange,
Predicate<String> suspendingPackagePredicate, int userId) {
final List<String> unsuspendedPackages = new ArrayList<>();
final IntArray unsuspendedUids = new IntArray();
synchronized (mLock) {
for (String packageName : packagesToChange) {
final PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
ps.removeSuspension(suspendingPackagePredicate, userId);
if (!ps.getUserStateOrDefault(userId).isSuspended()) {
unsuspendedPackages.add(ps.getPackageName());
unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
}
}
}
scheduleWritePackageRestrictionsLocked(userId);
}
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), userId);
}
}
void removeAllDistractingPackageRestrictions(int userId) {
final String[] allPackages = mComputer.getAllAvailablePackageNames();
removeDistractingPackageRestrictions(allPackages, userId);
@@ -4698,24 +4496,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
private boolean isCallerDeviceOrProfileOwner(int userId) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SYSTEM_UID) {
return true;
}
final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
if (ownerPackage != null) {
return callingUid == getPackageUidInternal(ownerPackage, 0, userId, callingUid);
}
return false;
}
private boolean isSuspendAllowedForUser(int userId) {
return isCallerDeviceOrProfileOwner(userId)
|| (!mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
}
@Override
public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
Objects.requireNonNull(packageNames, "packageNames cannot be null");
@@ -4726,125 +4506,8 @@ public class PackageManagerService extends IPackageManager.Stub
throw new SecurityException("Calling uid " + callingUid
+ " cannot query getUnsuspendablePackagesForUser for user " + userId);
}
if (!isSuspendAllowedForUser(userId)) {
Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
return packageNames;
}
final ArraySet<String> unactionablePackages = new ArraySet<>();
final boolean[] canSuspend = canSuspendPackageForUserInternal(packageNames, userId);
for (int i = 0; i < packageNames.length; i++) {
if (!canSuspend[i]) {
unactionablePackages.add(packageNames[i]);
continue;
}
synchronized (mLock) {
final PackageSetting ps = mSettings.getPackageLPr(packageNames[i]);
if (ps == null || shouldFilterApplication(ps, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
unactionablePackages.add(packageNames[i]);
}
}
}
return unactionablePackages.toArray(new String[unactionablePackages.size()]);
}
/**
* Returns an array of booleans, such that the ith boolean denotes whether the ith package can
* be suspended or not.
*
* @param packageNames The package names to check suspendability for.
* @param userId The user to check in
* @return An array containing results of the checks
*/
@NonNull
private boolean[] canSuspendPackageForUserInternal(@NonNull String[] packageNames, int userId) {
final boolean[] canSuspend = new boolean[packageNames.length];
final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
final long callingId = Binder.clearCallingIdentity();
try {
final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId);
for (int i = 0; i < packageNames.length; i++) {
canSuspend[i] = false;
final String packageName = packageNames[i];
if (isPackageDeviceAdmin(packageName, userId)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": has an active device admin");
continue;
}
if (packageName.equals(activeLauncherPackageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": contains the active launcher");
continue;
}
if (packageName.equals(mRequiredInstallerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package installation");
continue;
}
if (packageName.equals(mRequiredUninstallerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package uninstallation");
continue;
}
if (packageName.equals(mRequiredVerifierPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package verification");
continue;
}
if (packageName.equals(dialerPackageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": is the default dialer");
continue;
}
if (packageName.equals(mRequiredPermissionControllerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for permissions management");
continue;
}
synchronized (mLock) {
if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": protected package");
continue;
}
if (!isCallerOwner && mSettings.getBlockUninstallLPr(userId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": blocked by admin");
continue;
}
AndroidPackage pkg = mPackages.get(packageName);
if (pkg != null) {
// Cannot suspend SDK libs as they are controlled by SDK manager.
if (pkg.isSdkLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing SDK library: "
+ pkg.getSdkLibName());
continue;
}
// Cannot suspend static shared libs as they are considered
// a part of the using app (emulating static linking). Also
// static libs are installed always on internal storage.
if (pkg.isStaticSharedLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing static shared library: "
+ pkg.getStaticSharedLibName());
continue;
}
}
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
continue;
}
canSuspend[i] = true;
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
return canSuspend;
return mSuspendPackageHelper.getUnsuspendablePackagesForUser(
packageNames, userId, callingUid);
}
@Override
@@ -7420,41 +7083,27 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(packageName);
if (packageState == null) {
return null;
}
Bundle allExtras = new Bundle();
PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (userState.isSuspended()) {
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
final SuspendParams params = userState.getSuspendParams().valueAt(i);
if (params != null && params.getLauncherExtras() != null) {
allExtras.putAll(params.getLauncherExtras());
}
}
}
return (allExtras.size() > 0) ? allExtras : null;
return mSuspendPackageHelper.getSuspendedPackageLauncherExtras(
packageName, userId, Binder.getCallingUid());
}
@Override
public boolean isPackageSuspended(String packageName, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(packageName);
return packageState != null && packageState.getUserStateOrDefault(userId)
.isSuspended();
return mSuspendPackageHelper.isPackageSuspended(
packageName, userId, Binder.getCallingUid());
}
@Override
public void removeAllNonSystemPackageSuspensions(int userId) {
final String[] allPackages = mComputer.getAllAvailablePackageNames();
PackageManagerService.this.removeSuspensionsBySuspendingPackage(allPackages,
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages,
(suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
userId);
}
@Override
public void removeNonSystemPackageSuspensions(String packageName, int userId) {
PackageManagerService.this.removeSuspensionsBySuspendingPackage(
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
new String[]{packageName},
(suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
userId);
@@ -7480,46 +7129,15 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public String getSuspendingPackage(String suspendedPackage, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage);
if (packageState == null) {
return null;
}
final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (!userState.isSuspended()) {
return null;
}
String suspendingPackage = null;
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
suspendingPackage = userState.getSuspendParams().keyAt(i);
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
return suspendingPackage;
}
}
return suspendingPackage;
return mSuspendPackageHelper.getSuspendingPackage(
suspendedPackage, userId, Binder.getCallingUid());
}
@Override
public SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
String suspendingPackage, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage);
if (packageState == null) {
return null;
}
final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (!userState.isSuspended()) {
return null;
}
final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams();
if (suspendParamsMap == null) {
return null;
}
final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
return mSuspendPackageHelper.getSuspendedDialogInfo(
suspendedPackage, suspendingPackage, userId, Binder.getCallingUid());
}
@Override
@@ -9083,6 +8701,8 @@ public class PackageManagerService extends IPackageManager.Stub
return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
case PackageManagerInternal.PACKAGE_INSTALLER:
return mComputer.filterOnlySystemPackages(mRequiredInstallerPackage);
case PackageManagerInternal.PACKAGE_UNINSTALLER:
return mComputer.filterOnlySystemPackages(mRequiredUninstallerPackage);
case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
return mComputer.filterOnlySystemPackages(mSetupWizardPackage);
case PackageManagerInternal.PACKAGE_SYSTEM:

View File

@@ -111,4 +111,5 @@ public final class PackageManagerServiceTestParams {
public PreferredActivityHelper preferredActivityHelper;
public ResolveIntentHelper resolveIntentHelper;
public DexOptHelper dexOptHelper;
public SuspendPackageHelper suspendPackageHelper;
}

View File

@@ -0,0 +1,611 @@
/*
* 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.PackageManagerInternal.PACKAGE_INSTALLER;
import static android.content.pm.PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER;
import static android.content.pm.PackageManagerInternal.PACKAGE_UNINSTALLER;
import static android.content.pm.PackageManagerInternal.PACKAGE_VERIFIER;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Intent;
import android.content.pm.PackageManagerInternal.KnownPackage;
import android.content.pm.SuspendDialogInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public final class SuspendPackageHelper {
// TODO(b/198166813): remove PMS dependency
private final PackageManagerService mPm;
private final PackageManagerServiceInjector mInjector;
private final BroadcastHelper mBroadcastHelper;
private final ProtectedPackages mProtectedPackages;
/**
* Constructor for {@link PackageManagerService}.
*/
SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) {
mPm = pm;
mInjector = injector;
mBroadcastHelper = broadcastHelper;
mProtectedPackages = protectedPackages;
}
/**
* Updates the package to the suspended or unsuspended state.
*
* @param packageNames The names of the packages to set the suspended status.
* @param suspended {@code true} to suspend packages, or {@code false} to unsuspend packages.
* @param appExtras An optional {@link PersistableBundle} that the suspending app can provide
* which will be shared with the apps being suspended. Ignored if
* {@code suspended} is false.
* @param launcherExtras An optional {@link PersistableBundle} that the suspending app can
* provide which will be shared with the launcher. Ignored if
* {@code suspended} is false.
* @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
* should be shown to the user when they try to launch a suspended app.
* Ignored if {@code suspended} is false.
* @param callingPackage The caller's package name.
* @param userId The user where packages reside.
* @param callingUid The caller's uid.
* @return The names of failed packages.
*/
@Nullable
String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
@Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
@Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage,
int userId, int callingUid) {
if (ArrayUtils.isEmpty(packageNames)) {
return packageNames;
}
if (suspended && !isSuspendAllowedForUser(userId, callingUid)) {
Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
return packageNames;
}
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canSuspend =
suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null;
for (int i = 0; i < packageNames.length; i++) {
final String packageName = packageNames[i];
if (callingPackage.equals(packageName)) {
Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+ (suspended ? "" : "un") + "suspend itself. Ignoring");
unactionedPackages.add(packageName);
continue;
}
final PackageSetting pkgSetting;
synchronized (mPm.mLock) {
pkgSetting = mPm.mSettings.getPackageLPr(packageName);
if (pkgSetting == null
|| mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageName
+ ". Skipping suspending/un-suspending.");
unactionedPackages.add(packageName);
continue;
}
}
if (canSuspend != null && !canSuspend[i]) {
unactionedPackages.add(packageName);
continue;
}
final boolean packageUnsuspended;
final boolean packageModified;
synchronized (mPm.mLock) {
if (suspended) {
packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
dialogInfo, appExtras, launcherExtras, userId);
} else {
packageModified = pkgSetting.removeSuspension(callingPackage, userId);
}
packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
}
if (suspended || packageUnsuspended) {
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
}
if (packageModified) {
modifiedPackagesList.add(packageName);
modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
}
}
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
sendPackagesSuspendedForUser(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
synchronized (mPm.mLock) {
mPm.scheduleWritePackageRestrictionsLocked(userId);
}
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!modifiedPackagesList.isEmpty()) {
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
}
return unactionedPackages.toArray(new String[0]);
}
/**
* Returns the names in the {@code packageNames} which can not be suspended by the caller.
*
* @param packageNames The names of packages to check.
* @param userId The user where packages reside.
* @param callingUid The caller's uid.
* @return The names of packages which are Unsuspendable.
*/
@NonNull
String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId,
int callingUid) {
if (!isSuspendAllowedForUser(userId, callingUid)) {
Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
return packageNames;
}
final ArraySet<String> unactionablePackages = new ArraySet<>();
final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid);
for (int i = 0; i < packageNames.length; i++) {
if (!canSuspend[i]) {
unactionablePackages.add(packageNames[i]);
continue;
}
synchronized (mPm.mLock) {
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]);
if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
unactionablePackages.add(packageNames[i]);
}
}
}
return unactionablePackages.toArray(new String[unactionablePackages.size()]);
}
/**
* Returns the app extras of the given suspended package.
*
* @param packageName The suspended package name.
* @param userId The user where the package resides.
* @param callingUid The caller's uid.
* @return The app extras of the suspended package.
*/
@Nullable
Bundle getSuspendedPackageAppExtras(@NonNull String packageName, int userId, int callingUid) {
final PackageStateInternal ps = mPm.getPackageStateInternal(packageName, callingUid);
if (ps == null) {
return null;
}
final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId);
final Bundle allExtras = new Bundle();
if (pus.isSuspended()) {
for (int i = 0; i < pus.getSuspendParams().size(); i++) {
final SuspendParams params = pus.getSuspendParams().valueAt(i);
if (params != null && params.getAppExtras() != null) {
allExtras.putAll(params.getAppExtras());
}
}
}
return (allExtras.size() > 0) ? allExtras : null;
}
/**
* Removes any suspensions on given packages that were added by packages that pass the given
* predicate.
*
* <p> Caller must flush package restrictions if it cares about immediate data consistency.
*
* @param packagesToChange The packages on which the suspension are to be removed.
* @param suspendingPackagePredicate A predicate identifying the suspending packages whose
* suspensions will be removed.
* @param userId The user for which the changes are taking place.
*/
void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange,
@NonNull Predicate<String> suspendingPackagePredicate, int userId) {
final List<String> unsuspendedPackages = new ArrayList<>();
final IntArray unsuspendedUids = new IntArray();
synchronized (mPm.mLock) {
for (String packageName : packagesToChange) {
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
ps.removeSuspension(suspendingPackagePredicate, userId);
if (!ps.getUserStateOrDefault(userId).isSuspended()) {
unsuspendedPackages.add(ps.getPackageName());
unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
}
}
}
mPm.scheduleWritePackageRestrictionsLocked(userId);
}
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), userId);
}
}
/**
* Returns the launcher extras for the given suspended package.
*
* @param packageName The name of the suspended package.
* @param userId The user where the package resides.
* @param callingUid The caller's uid.
* @return The launcher extras.
*/
@Nullable
Bundle getSuspendedPackageLauncherExtras(@NonNull String packageName, int userId,
int callingUid) {
final PackageStateInternal packageState = mPm.getPackageStateInternal(
packageName, callingUid);
if (packageState == null) {
return null;
}
Bundle allExtras = new Bundle();
PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (userState.isSuspended()) {
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
final SuspendParams params = userState.getSuspendParams().valueAt(i);
if (params != null && params.getLauncherExtras() != null) {
allExtras.putAll(params.getLauncherExtras());
}
}
}
return (allExtras.size() > 0) ? allExtras : null;
}
/**
* Return {@code true}, if the given package is suspended.
*
* @param packageName The name of package to check.
* @param userId The user where the package resides.
* @param callingUid The caller's uid.
* @return {@code true}, if the given package is suspended.
*/
boolean isPackageSuspended(@NonNull String packageName, int userId, int callingUid) {
final PackageStateInternal packageState = mPm.getPackageStateInternal(
packageName, callingUid);
return packageState != null && packageState.getUserStateOrDefault(userId)
.isSuspended();
}
/**
* Given a suspended package, returns the name of package which invokes suspending to it.
*
* @param suspendedPackage The suspended package to check.
* @param userId The user where the package resides.
* @param callingUid The caller's uid.
* @return The name of suspending package.
*/
@Nullable
String getSuspendingPackage(@NonNull String suspendedPackage, int userId, int callingUid) {
final PackageStateInternal packageState = mPm.getPackageStateInternal(
suspendedPackage, callingUid);
if (packageState == null) {
return null;
}
final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (!userState.isSuspended()) {
return null;
}
String suspendingPackage = null;
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
suspendingPackage = userState.getSuspendParams().keyAt(i);
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
return suspendingPackage;
}
}
return suspendingPackage;
}
/**
* Returns the dialog info of the given suspended package.
*
* @param suspendedPackage The name of the suspended package.
* @param suspendingPackage The name of the suspending package.
* @param userId The user where the package resides.
* @param callingUid The caller's uid.
* @return The dialog info.
*/
@Nullable
SuspendDialogInfo getSuspendedDialogInfo(@NonNull String suspendedPackage,
@NonNull String suspendingPackage, int userId, int callingUid) {
final PackageStateInternal packageState = mPm.getPackageStateInternal(
suspendedPackage, callingUid);
if (packageState == null) {
return null;
}
final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
if (!userState.isSuspended()) {
return null;
}
final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams();
if (suspendParamsMap == null) {
return null;
}
final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
}
/**
* Return {@code true} if the user is allowed to suspend packages by the caller.
*
* @param userId The user id to check.
* @param callingUid The caller's uid.
* @return {@code true} if the user is allowed to suspend packages by the caller.
*/
boolean isSuspendAllowedForUser(int userId, int callingUid) {
final UserManagerService userManager = mInjector.getUserManagerService();
return isCallerDeviceOrProfileOwner(userId, callingUid)
|| (!userManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
&& !userManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
}
/**
* Returns an array of booleans, such that the ith boolean denotes whether the ith package can
* be suspended or not.
*
* @param packageNames The package names to check suspendability for.
* @param userId The user to check in
* @param callingUid The caller's uid.
* @return An array containing results of the checks
*/
@NonNull
boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) {
final boolean[] canSuspend = new boolean[packageNames.length];
final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid);
final long token = Binder.clearCallingIdentity();
try {
final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
final String requiredInstallerPackage = getKnownPackageName(PACKAGE_INSTALLER, userId);
final String requiredUninstallerPackage =
getKnownPackageName(PACKAGE_UNINSTALLER, userId);
final String requiredVerifierPackage = getKnownPackageName(PACKAGE_VERIFIER, userId);
final String requiredPermissionControllerPackage =
getKnownPackageName(PACKAGE_PERMISSION_CONTROLLER, userId);
for (int i = 0; i < packageNames.length; i++) {
canSuspend[i] = false;
final String packageName = packageNames[i];
if (mPm.isPackageDeviceAdmin(packageName, userId)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": has an active device admin");
continue;
}
if (packageName.equals(activeLauncherPackageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": contains the active launcher");
continue;
}
if (packageName.equals(requiredInstallerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package installation");
continue;
}
if (packageName.equals(requiredUninstallerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package uninstallation");
continue;
}
if (packageName.equals(requiredVerifierPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for package verification");
continue;
}
if (packageName.equals(dialerPackageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": is the default dialer");
continue;
}
if (packageName.equals(requiredPermissionControllerPackage)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": required for permissions management");
continue;
}
synchronized (mPm.mLock) {
if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": protected package");
continue;
}
if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": blocked by admin");
continue;
}
AndroidPackage pkg = mPm.mPackages.get(packageName);
if (pkg != null) {
// Cannot suspend SDK libs as they are controlled by SDK manager.
if (pkg.isSdkLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing SDK library: "
+ pkg.getSdkLibName());
continue;
}
// Cannot suspend static shared libs as they are considered
// a part of the using app (emulating static linking). Also
// static libs are installed always on internal storage.
if (pkg.isStaticSharedLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
+ " providing static shared library: "
+ pkg.getStaticSharedLibName());
continue;
}
}
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
continue;
}
canSuspend[i] = true;
}
} finally {
Binder.restoreCallingIdentity(token);
}
return canSuspend;
}
/**
* Send broadcast intents for packages suspension changes.
*
* @param intent The action name of the suspension intent.
* @param pkgList The names of packages which have suspension changes.
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
@NonNull int[] uidList, int userId) {
final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
final int[] userIds = new int[] {userId};
// Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
// allow lists are the same.
for (int i = 0; i < pkgList.length; i++) {
final String pkgName = pkgList[i];
final int uid = uidList[i];
SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList(
mPm.getPackageStateInternal(pkgName, SYSTEM_UID),
userIds, mPm.getPackageStates());
if (allowList == null) {
allowList = new SparseArray<>(0);
}
boolean merged = false;
for (int j = 0; j < allowListsToSend.size(); j++) {
if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
pkgsToSend.get(j).add(pkgName);
uidsToSend.get(j).add(uid);
merged = true;
break;
}
}
if (!merged) {
pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
uidsToSend.add(IntArray.wrap(new int[] {uid}));
allowListsToSend.add(allowList);
}
}
final Handler handler = mInjector.getHandler();
for (int i = 0; i < pkgsToSend.size(); i++) {
final Bundle extras = new Bundle(3);
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
? null : allowListsToSend.get(i);
handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
null /* finishedReceiver */, userIds, null /* instantUserIds */,
allowList, null /* bOptions */));
}
}
private String getKnownPackageName(@KnownPackage int knownPackage, int userId) {
final String[] knownPackages = mPm.getKnownPackageNamesInternal(knownPackage, userId);
return knownPackages.length > 0 ? knownPackages[0] : null;
}
private boolean isCallerDeviceOrProfileOwner(int userId, int callingUid) {
if (callingUid == SYSTEM_UID) {
return true;
}
final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
if (ownerPackage != null) {
return callingUid == mPm.getPackageUidInternal(
ownerPackage, 0, userId, callingUid);
}
return false;
}
private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
int userId) {
final Handler handler = mInjector.getHandler();
final String action = suspended
? Intent.ACTION_MY_PACKAGE_SUSPENDED
: Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
handler.post(() -> {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+ (suspended ? "" : "UN") + "SUSPENDED broadcasts");
return;
}
final int[] targetUserIds = new int[] {userId};
for (String packageName : affectedPackages) {
final Bundle appExtras = suspended
? getSuspendedPackageAppExtras(packageName, userId, SYSTEM_UID)
: null;
final Bundle intentExtras;
if (appExtras != null) {
intentExtras = new Bundle(1);
intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
} else {
intentExtras = null;
}
handler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
targetUserIds, false, null, null));
}
});
}
}

View File

@@ -216,6 +216,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
val displayMetrics: DisplayMetrics = mock()
val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
val handler = TestHandler(null)
val defaultAppProvider: DefaultAppProvider = mock()
}
companion object {
@@ -294,6 +295,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
whenever(mocks.injector.domainVerificationManagerInternal)
.thenReturn(mocks.domainVerificationManagerInternal)
whenever(mocks.injector.handler) { mocks.handler }
whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)

View File

@@ -0,0 +1,507 @@
/*
* 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 android.content.Intent
import android.content.pm.PackageManagerInternal
import android.content.pm.SuspendDialogInfo
import android.os.Binder
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.os.UserHandle
import android.os.UserManager
import android.util.ArrayMap
import android.util.SparseArray
import com.android.server.pm.pkg.PackageStateInternal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.argThat
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
class SuspendPackageHelperTest {
companion object {
const val TEST_PACKAGE_1 = "com.android.test.package1"
const val TEST_PACKAGE_2 = "com.android.test.package2"
const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent"
const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin"
const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home"
const val DIALER_PACKAGE = "com.android.test.known.dialer"
const val INSTALLER_PACKAGE = "com.android.test.known.installer"
const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
const val TEST_USER_ID = 0
}
lateinit var pms: PackageManagerService
lateinit var suspendPackageHelper: SuspendPackageHelper
lateinit var testHandler: TestHandler
lateinit var defaultAppProvider: DefaultAppProvider
lateinit var packageSetting1: PackageStateInternal
lateinit var packageSetting2: PackageStateInternal
lateinit var ownerSetting: PackageStateInternal
lateinit var packagesToSuspend: Array<String>
lateinit var uidsToSuspend: IntArray
@Mock
lateinit var broadcastHelper: BroadcastHelper
@Mock
lateinit var protectedPackages: ProtectedPackages
@Captor
lateinit var bundleCaptor: ArgumentCaptor<Bundle>
@Rule
@JvmField
val rule = MockSystemRule()
var deviceOwnerUid = 0
@Before
@Throws(Exception::class)
fun setup() {
MockitoAnnotations.initMocks(this)
rule.system().stageNominalSystemState()
pms = spy(createPackageManagerService(
TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE,
DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE,
VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE))
suspendPackageHelper = SuspendPackageHelper(
pms, rule.mocks().injector, broadcastHelper, protectedPackages)
defaultAppProvider = rule.mocks().defaultAppProvider
testHandler = rule.mocks().handler
packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!!
packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!!
ownerSetting = pms.getPackageStateInternal(DEVICE_OWNER_PACKAGE)!!
deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId)
packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID)))
.thenReturn(DEVICE_OWNER_PACKAGE)
whenever(rule.mocks().userManagerService.hasUserRestriction(
eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true)
whenever(rule.mocks().userManagerService.hasUserRestriction(
eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
mockKnownPackages(pms)
}
@Test
fun setPackagesSuspended() {
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(failedNames).isEmpty()
}
@Test
fun setPackagesSuspended_emptyPackageName() {
var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */,
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
assertThat(failedNames).isNull()
failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
assertThat(failedNames).isEmpty()
}
@Test
fun setPackagesSuspended_callerIsNotAllowed() {
val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid())
assertThat(failedNames).asList().hasSize(1)
assertThat(failedNames).asList().contains(TEST_PACKAGE_2)
}
@Test
fun setPackagesSuspended_callerSuspendItself() {
val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
assertThat(failedNames).asList().hasSize(1)
assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE)
}
@Test
fun setPackagesSuspended_nonexistentPackage() {
val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
assertThat(failedNames).asList().hasSize(1)
assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE)
}
@Test
fun setPackagesSuspended_knownPackages() {
val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages,
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
assertThat(failedNames.size).isEqualTo(knownPackages.size)
for (pkg in knownPackages) {
assertThat(failedNames).asList().contains(pkg)
}
}
@Test
fun setPackagesUnsuspended() {
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
false /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(failedNames).isEmpty()
}
@Test
fun getUnsuspendablePackagesForUser() {
val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid)
assertThat(results.size).isEqualTo(unsuspendables.size)
for (pkg in unsuspendables) {
assertThat(results).asList().contains(pkg)
}
}
@Test
fun getUnsuspendablePackagesForUser_callerIsNotAllowed() {
val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
suspendables, TEST_USER_ID, Binder.getCallingUid())
assertThat(results.size).isEqualTo(suspendables.size)
for (pkg in suspendables) {
assertThat(results).asList().contains(pkg)
}
}
@Test
fun getSuspendedPackageAppExtras() {
val appExtras = PersistableBundle()
appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
true /* suspended */, appExtras, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
val result = suspendPackageHelper.getSuspendedPackageAppExtras(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!!
assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1)
}
@Test
fun removeSuspensionsBySuspendingPackage() {
val appExtras = PersistableBundle()
appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2)
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
true /* suspended */, appExtras, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.getSuspendingPackage(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
assertThat(suspendPackageHelper.getSuspendingPackage(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages,
{ suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID)
testHandler.flush()
verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
nullable(), nullable())
assertThat(suspendPackageHelper.getSuspendingPackage(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
assertThat(suspendPackageHelper.getSuspendingPackage(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
}
@Test
fun getSuspendedPackageLauncherExtras() {
val launcherExtras = PersistableBundle()
launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
true /* suspended */, null /* appExtras */, launcherExtras,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
val result = suspendPackageHelper.getSuspendedPackageLauncherExtras(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)!!
assertThat(result.getString(TEST_PACKAGE_2)).isEqualTo(TEST_PACKAGE_2)
}
@Test
fun isPackageSuspended() {
var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.isPackageSuspended(
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isTrue()
}
@Test
fun getSuspendingPackage() {
val launcherExtras = PersistableBundle()
launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
true /* suspended */, null /* appExtras */, launcherExtras,
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.getSuspendingPackage(
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
}
@Test
fun getSuspendedDialogInfo() {
val dialogInfo = SuspendDialogInfo.Builder()
.setTitle(TEST_PACKAGE_1).build()
var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
true /* suspended */, null /* appExtras */, null /* launcherExtras */,
dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
assertThat(failedNames).isEmpty()
val result = suspendPackageHelper.getSuspendedDialogInfo(
TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper, times(2)).sendPackageBroadcast(
any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), any(), nullable())
bundleCaptor.allValues.forEach {
var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages?.size).isEqualTo(1)
assertThat(changedUids?.size).isEqualTo(1)
assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
}
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, null)
suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper, times(2)).sendPackageBroadcast(
any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
bundleCaptor.allValues.forEach {
var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages?.size).isEqualTo(1)
assertThat(changedUids?.size).isEqualTo(1)
assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
}
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(
eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(modifiedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
this.put(TEST_USER_ID, uids)
}
private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
any() as ArrayMap<String, out PackageStateInternal>
))
.thenReturn(list)
}
private fun mockKnownPackages(pms: PackageManagerService) {
Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms)
.isPackageDeviceAdmin(any(), any())
Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider)
.getDefaultHome(eq(TEST_USER_ID))
Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider)
.getDefaultDialer(eq(TEST_USER_ID))
Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
eq(PackageManagerInternal.PACKAGE_INSTALLER), eq(TEST_USER_ID))
Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
eq(PackageManagerInternal.PACKAGE_UNINSTALLER), eq(TEST_USER_ID))
Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
eq(PackageManagerInternal.PACKAGE_VERIFIER), eq(TEST_USER_ID))
Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
.getKnownPackageNamesInternal(
eq(PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
}
private fun createPackageManagerService(vararg stageExistingPackages: String):
PackageManagerService {
stageExistingPackages.forEach {
rule.system().stageScanExistingPackage(it, 1L,
rule.system().dataAppDirectory)
}
var pms = PackageManagerService(rule.mocks().injector,
false /*coreOnly*/,
false /*factoryTest*/,
MockSystem.DEFAULT_VERSION_INFO.fingerprint,
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
Build.VERSION.INCREMENTAL,
false /*snapshotEnabled*/)
rule.system().validateFinalState()
return pms
}
}

View File

@@ -1,184 +0,0 @@
/*
* 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 android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.ArrayMap
import android.util.SparseArray
import com.android.server.pm.pkg.PackageStateInternal
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mockito.argThat
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
class SuspendPackagesBroadcastTest {
companion object {
const val TEST_PACKAGE_1 = "com.android.test.package1"
const val TEST_PACKAGE_2 = "com.android.test.package2"
const val TEST_USER_ID = 0
}
lateinit var pms: PackageManagerService
lateinit var packageSetting1: PackageStateInternal
lateinit var packageSetting2: PackageStateInternal
lateinit var packagesToSuspend: Array<String>
lateinit var uidsToSuspend: IntArray
@Captor
lateinit var bundleCaptor: ArgumentCaptor<Bundle>
@Rule
@JvmField
val rule = MockSystemRule()
@Before
@Throws(Exception::class)
fun setup() {
MockitoAnnotations.initMocks(this)
rule.system().stageNominalSystemState()
pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!!
packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!!
packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
bundleCaptor.allValues.forEach {
var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages?.size).isEqualTo(1)
assertThat(changedUids?.size).isEqualTo(1)
assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
}
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, null)
pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
bundleCaptor.allValues.forEach {
var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(changedPackages?.size).isEqualTo(1)
assertThat(changedUids?.size).isEqualTo(1)
assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
}
}
@Test
@Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms).sendPackageBroadcast(
eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
assertThat(modifiedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
this.put(TEST_USER_ID, uids)
}
private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
any() as ArrayMap<String, out PackageStateInternal>
))
.thenReturn(list)
}
private fun createPackageManagerService(vararg stageExistingPackages: String):
PackageManagerService {
stageExistingPackages.forEach {
rule.system().stageScanExistingPackage(it, 1L,
rule.system().dataAppDirectory)
}
var pms = PackageManagerService(rule.mocks().injector,
false /*coreOnly*/,
false /*factoryTest*/,
MockSystem.DEFAULT_VERSION_INFO.fingerprint,
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
Build.VERSION.INCREMENTAL,
false /*snapshotEnabled*/)
rule.system().validateFinalState()
return pms
}
}