Move logics of suspending packages out of package manager service
Bug: 201334238 Test: atest com.android.server.pm.SuspendPackagesTest Test: atest CtsSuspendAppsTestCases Test: atest CtsSuspendAppsPermissionTestCases Test: atest SuspendPackageHelperTest Change-Id: I88940b68b07ea0acdac4fcce6649431e4bb1e4b8
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -111,4 +111,5 @@ public final class PackageManagerServiceTestParams {
|
||||
public PreferredActivityHelper preferredActivityHelper;
|
||||
public ResolveIntentHelper resolveIntentHelper;
|
||||
public DexOptHelper dexOptHelper;
|
||||
public SuspendPackageHelper suspendPackageHelper;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user