From f727d0dc3ca592ab0e0e556f56b15f364deef9bf Mon Sep 17 00:00:00 2001 From: Rhed Jao Date: Tue, 18 Jan 2022 20:49:26 +0800 Subject: [PATCH] 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 --- .../content/pm/PackageManagerInternal.java | 32 +- .../server/pm/PackageManagerService.java | 440 +------------ .../pm/PackageManagerServiceTestParams.java | 1 + .../server/pm/SuspendPackageHelper.java | 611 ++++++++++++++++++ .../src/com/android/server/pm/MockSystem.kt | 2 + .../server/pm/SuspendPackageHelperTest.kt | 507 +++++++++++++++ .../server/pm/SuspendPackagesBroadcastTest.kt | 184 ------ 7 files changed, 1169 insertions(+), 608 deletions(-) create mode 100644 services/core/java/com/android/server/pm/SuspendPackageHelper.java create mode 100644 services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt delete mode 100644 services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 60cae4d67f5a0..1666d15b2387f 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -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: diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 13f91e0dca627..81a2c0046adfa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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> pkgsToSend = new ArrayList(pkgList.length); - final List uidsToSend = new ArrayList(pkgList.length); - final List> 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 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 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 changedPackagesList = new ArrayList<>(packageNames.length); final IntArray changedUids = new IntArray(packageNames.length); final List 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 changedPackagesList = new ArrayList<>(packageNames.length); - final IntArray changedUids = new IntArray(packageNames.length); - final List modifiedPackagesList = new ArrayList<>(packageNames.length); - final IntArray modifiedUids = new IntArray(packageNames.length); - final List 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. - * - *

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 suspendingPackagePredicate, int userId) { - final List 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 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 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: diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index a1acc388146ed..0d6555c52623a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -111,4 +111,5 @@ public final class PackageManagerServiceTestParams { public PreferredActivityHelper preferredActivityHelper; public ResolveIntentHelper resolveIntentHelper; public DexOptHelper dexOptHelper; + public SuspendPackageHelper suspendPackageHelper; } diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java new file mode 100644 index 0000000000000..f466ca72f681f --- /dev/null +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -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 changedPackagesList = new ArrayList<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); + final List modifiedPackagesList = new ArrayList<>(packageNames.length); + final IntArray modifiedUids = new IntArray(packageNames.length); + final List 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 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. + * + *

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 suspendingPackagePredicate, int userId) { + final List 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 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> pkgsToSend = new ArrayList(pkgList.length); + final List uidsToSend = new ArrayList(pkgList.length); + final List> 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 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 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)); + } + }); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index c2e0a04e3caa9..555f4b8b5cac1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -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) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt new file mode 100644 index 0000000000000..fe7e2d9eb0475 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -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 + lateinit var uidsToSuspend: IntArray + + @Mock + lateinit var broadcastHelper: BroadcastHelper + @Mock + lateinit var protectedPackages: ProtectedPackages + + @Captor + lateinit var bundleCaptor: ArgumentCaptor + + @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().apply { + this.put(TEST_USER_ID, uids) + } + + private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray?) { + whenever(rule.mocks().appsFilter.getVisibilityAllowList( + argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), + any() as ArrayMap + )) + .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 + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt deleted file mode 100644 index 02ee35b9e7a80..0000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt +++ /dev/null @@ -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 - lateinit var uidsToSuspend: IntArray - - @Captor - lateinit var bundleCaptor: ArgumentCaptor - - @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().apply { - this.put(TEST_USER_ID, uids) - } - - private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray?) { - whenever(rule.mocks().appsFilter.getVisibilityAllowList( - argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java), - any() as ArrayMap - )) - .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 - } -}