Unsuspending packages when a PO or DO is set
If a profile or device owner is present, package manager does not
support calls to PM#setPackagesSuspended. So if there were already
suspended packages, they need to be unsuspended when a PO or DO is
added.
Test: atest com.android.server.pm.SuspendPackagesTest
Bug: 79980390
Change-Id: Ib79d95ccc25eef534fdcd540d3d6ea75c31982a7
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f4853f3..968eb41 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -367,6 +367,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
/**
* Keep track of all those APKs everywhere.
@@ -14234,28 +14235,50 @@
* @param packageName The package holding {@link Manifest.permission#SUSPEND_APPS} permission
* @param affectedUser The user for which the changes are taking place.
*/
- void unsuspendForSuspendingPackage(String packageName, int affectedUser) {
+ void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
final int[] userIds = (affectedUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
: new int[] {affectedUser};
for (int userId : userIds) {
- List<String> affectedPackages = new ArrayList<>();
- synchronized (mPackages) {
- for (PackageSetting ps : mSettings.mPackages.values()) {
- final PackageUserState pus = ps.readUserState(userId);
- if (pus.suspended && packageName.equals(pus.suspendingPackage)) {
- ps.setSuspended(false, null, null, null, null, userId);
- affectedPackages.add(ps.name);
- }
+ unsuspendForSuspendingPackages(packageName::equals, userId);
+ }
+ }
+
+ /**
+ * Immediately unsuspends any packages in the given users not suspended by the platform or root.
+ * To be called when a profile owner or a device owner is added.
+ *
+ * <p><b>Should not be used on a frequent code path</b> as it flushes state to disk
+ * synchronously
+ *
+ * @param userIds The users for which to unsuspend packages
+ */
+ void unsuspendForNonSystemSuspendingPackages(ArraySet<Integer> userIds) {
+ final int sz = userIds.size();
+ for (int i = 0; i < sz; i++) {
+ unsuspendForSuspendingPackages(
+ (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
+ userIds.valueAt(i));
+ }
+ }
+
+ private void unsuspendForSuspendingPackages(Predicate<String> packagePredicate, int userId) {
+ final List<String> affectedPackages = new ArrayList<>();
+ synchronized (mPackages) {
+ for (PackageSetting ps : mSettings.mPackages.values()) {
+ final PackageUserState pus = ps.readUserState(userId);
+ if (pus.suspended && packagePredicate.test(pus.suspendingPackage)) {
+ ps.setSuspended(false, null, null, null, null, userId);
+ affectedPackages.add(ps.name);
}
}
- if (!affectedPackages.isEmpty()) {
- final String[] packageArray = affectedPackages.toArray(
- new String[affectedPackages.size()]);
- sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
- sendPackagesSuspendedForUser(packageArray, userId, false, null);
- // Write package restrictions immediately to avoid an inconsistent state.
- mSettings.writePackageRestrictionsLPr(userId);
- }
+ }
+ if (!affectedPackages.isEmpty()) {
+ final String[] packageArray = affectedPackages.toArray(
+ new String[affectedPackages.size()]);
+ sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
+ sendPackagesSuspendedForUser(packageArray, userId, false, null);
+ // Write package restrictions immediately to avoid an inconsistent state.
+ mSettings.writePackageRestrictionsLPr(userId);
}
}
@@ -23953,6 +23976,18 @@
SparseArray<String> profileOwnerPackages) {
mProtectedPackages.setDeviceAndProfileOwnerPackages(
deviceOwnerUserId, deviceOwnerPackage, profileOwnerPackages);
+
+ final ArraySet<Integer> usersWithPoOrDo = new ArraySet<>();
+ if (deviceOwnerPackage != null) {
+ usersWithPoOrDo.add(deviceOwnerUserId);
+ }
+ final int sz = profileOwnerPackages.size();
+ for (int i = 0; i < sz; i++) {
+ if (profileOwnerPackages.valueAt(i) != null) {
+ usersWithPoOrDo.add(profileOwnerPackages.keyAt(i));
+ }
+ }
+ unsuspendForNonSystemSuspendingPackages(usersWithPoOrDo);
}
@Override
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 2ac9df9..a8efe81 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -70,7 +70,7 @@
<uses-sdk android:minSdkVersion="1"
android:targetSdkVersion="26"/>
- <application>
+ <application android:testOnly="true">
<uses-library android:name="android.test.runner" />
<service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService"
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 8f989df..5ac68d4 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
<option name="test-file-name" value="JobTestApp.apk" />
<option name="test-file-name" value="ConnTestApp.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index b8922eb..c186e48 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.AppGlobals;
import android.content.BroadcastReceiver;
@@ -59,6 +60,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.SynchronousQueue;
@@ -97,6 +99,9 @@
private AppCommunicationReceiver mAppCommsReceiver;
private StubbedCallback mTestCallback;
private UiDevice mUiDevice;
+ private ComponentName mDeviceAdminComponent;
+ private boolean mPoSet;
+ private boolean mDoSet;
private static final class AppCommunicationReceiver extends BroadcastReceiver {
private Context context;
@@ -163,6 +168,8 @@
mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
mReceiverHandler = new Handler(Looper.getMainLooper());
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mDeviceAdminComponent = new ComponentName(mContext,
+ "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1");
IPackageManager ipm = AppGlobals.getPackageManager();
try {
// Otherwise implicit broadcasts will not be delivered.
@@ -469,12 +476,83 @@
TEST_APP_PACKAGE_NAME, receivedPackageName);
}
+ private boolean setProfileOwner() throws IOException {
+ final String result = mUiDevice.executeShellCommand("dpm set-profile-owner --user cur "
+ + mDeviceAdminComponent.flattenToString());
+ return mPoSet = result.trim().startsWith("Success");
+ }
+
+ private boolean setDeviceOwner() throws IOException {
+ final String result = mUiDevice.executeShellCommand("dpm set-device-owner --user cur "
+ + mDeviceAdminComponent.flattenToString());
+ return mDoSet = result.trim().startsWith("Success");
+ }
+
+ private void removeProfileOrDeviceOwner() throws IOException {
+ if (mPoSet || mDoSet) {
+ mUiDevice.executeShellCommand("dpm remove-active-admin --user cur "
+ + mDeviceAdminComponent.flattenToString());
+ mPoSet = mDoSet = false;
+ }
+ }
+
+ @Test
+ public void testCannotSuspendWhenProfileOwner() throws IOException {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+ assertTrue("Profile-owner could not be set", setProfileOwner());
+ try {
+ suspendTestPackage(null, null, null);
+ fail("Suspend succeeded. Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException uex) {
+ }
+ }
+
+ @Test
+ public void testCannotSuspendWhenDeviceOwner() throws IOException {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+ assertTrue("Device-owner could not be set", setDeviceOwner());
+ try {
+ suspendTestPackage(null, null, null);
+ fail("Suspend succeeded. Expected UnsupportedOperationException");
+ } catch (UnsupportedOperationException uex) {
+ }
+ }
+
+ @Test
+ public void testPackageUnsuspendedOnAddingDeviceOwner() throws IOException {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED,
+ ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+ mAppCommsReceiver.drainPendingBroadcasts();
+ suspendTestPackage(null, null, null);
+ Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+ assertTrue("Device-owner could not be set", setDeviceOwner());
+ intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
+ }
+
+ @Test
+ public void testPackageUnsuspendedOnAddingProfileOwner() throws IOException {
+ assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
+ mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED,
+ ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+ mAppCommsReceiver.drainPendingBroadcasts();
+ suspendTestPackage(null, null, null);
+ Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
+ assertTrue("Profile-owner could not be set", setProfileOwner());
+ intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+ assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
+ }
+
@After
- public void tearDown() {
+ public void tearDown() throws IOException {
mAppCommsReceiver.unregister();
if (mTestCallback != null) {
mLauncherApps.unregisterCallback(mTestCallback);
}
+ removeProfileOrDeviceOwner();
mContext.sendBroadcast(new Intent(ACTION_FINISH_TEST_ACTIVITY)
.setPackage(TEST_APP_PACKAGE_NAME));
}