Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80b76383 authored by Pavel Grafov's avatar Pavel Grafov
Browse files

Convert personal app suspension on COMP->COPE migration

* if no apps are suspended by the DO prior to migration, nothing
  changes
* if some apps were suspended by the DO and the DPC targets R+
  via DPM.setPackagesSuspended(), this will result in personal
  apps suspended explicitly by the PO DPC as if it called
  DPM.setPersonalAppsSuspended(). The apps will stay suspended.
* if the DPC target SDK is below R, the apps will be unsuspended
  because the DPC won't have a way to unsuspend them. And the
  user will be stuck with suspended apps.

+ when unsuspending apps, don't collect the list of apps subject
  to suspension, but rather unsuspend all that is suspended. It
  is more robust, e.g. when some app stops meeting the
  conditions, e.g. not SMS app anymore.

Bug: 157270093
Test: com.android.server.devicepolicy.DevicePolicyManagerServiceMigrationTest
Test: Manual, with TestDPC, also patching it to target R

Change-Id: I1eba7216dd557c94bef822b77d25b484dfcd6f63
parent 2ad21ed9
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -988,4 +988,14 @@ public abstract class PackageManagerInternal {
     * Unblocks uninstall for all packages for the user.
     */
    public abstract void clearBlockUninstallForUser(@UserIdInt int userId);

    /**
     * Unsuspends all packages suspended by the given package for the user.
     */
    public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);

    /**
     * Returns {@code true} if the package is suspending any packages for the user.
     */
    public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
}
+21 −1
Original line number Diff line number Diff line
@@ -13490,6 +13490,17 @@ public class PackageManagerService extends IPackageManager.Stub
        removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId);
    }
    boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
        synchronized (mLock) {
            for (final PackageSetting ps : mSettings.mPackages.values()) {
                if (ps.isSuspendedBy(suspendingPackage, userId)) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Removes any suspensions on given packages that were added by packages that pass the given
     * predicate.
@@ -23885,7 +23896,6 @@ public class PackageManagerService extends IPackageManager.Stub
                    callingUid);
        }
        @Override
        public boolean isPlatformSigned(String packageName) {
            PackageSetting packageSetting = mSettings.mPackages.get(packageName);
@@ -24990,6 +25000,16 @@ public class PackageManagerService extends IPackageManager.Stub
                mSettings.writePackageRestrictionsLPr(userId);
            }
        }
        @Override
        public void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
            PackageManagerService.this.unsuspendForSuspendingPackage(packageName, affectedUser);
        }
        @Override
        public boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
            return PackageManagerService.this.isSuspendingAnyPackages(suspendingPackage, userId);
        }
    }
    @GuardedBy("mLock")
+5 −0
Original line number Diff line number Diff line
@@ -422,6 +422,11 @@ public abstract class PackageSettingBase extends SettingBase {
        return readUserState(userId).suspended;
    }

    boolean isSuspendedBy(String suspendingPackage, int userId) {
        final PackageUserState state = readUserState(userId);
        return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
    }

    void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
            PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
        final PackageUserState existingUserState = modifyUserState(userId);
+43 −12
Original line number Diff line number Diff line
@@ -2742,7 +2742,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        Slog.i(LOG_TAG, "Giving the PO additional power...");
        markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
        Slog.i(LOG_TAG, "Migrating DO policies to PO...");
        moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin());
        moveDoPoliciesToProfileParentAdminLocked(doAdmin, poAdmin.getParentActiveAdmin());
        migratePersonalAppSuspensionLocked(doUserId, poUserId, poAdmin);
        saveSettingsLocked(poUserId);
        Slog.i(LOG_TAG, "Clearing the DO...");
        final ComponentName doAdminReceiver = doAdmin.info.getComponent();
@@ -2762,6 +2763,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                .write();
    }
    @GuardedBy("getLockObject()")
    private void migratePersonalAppSuspensionLocked(
            int doUserId, int poUserId, ActiveAdmin poAdmin) {
        final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
        if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) {
            Slog.i(LOG_TAG, "DO is not suspending any apps.");
            return;
        }
        if (getTargetSdk(poAdmin.info.getPackageName(), poUserId) >= Build.VERSION_CODES.R) {
            Slog.i(LOG_TAG, "PO is targeting R+, keeping personal apps suspended.");
            getUserData(doUserId).mAppsSuspended = true;
            poAdmin.mSuspendPersonalApps = true;
        } else {
            Slog.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps.");
            pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId);
        }
    }
    private void uninstallOrDisablePackage(String packageName, int userHandle) {
        final ApplicationInfo appInfo;
        try {
@@ -2803,7 +2823,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
    }
    private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
    @GuardedBy("getLockObject()")
    private void moveDoPoliciesToProfileParentAdminLocked(
            ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
        // The following policies can be already controlled via parent instance, skip if so.
        if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
            parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy;
@@ -16132,25 +16154,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
        Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
                suspended ? "Suspending" : "Unsuspending", userId));
        if (suspended) {
            suspendPersonalAppsInPackageManager(userId);
        } else {
            mInjector.getPackageManagerInternal().unsuspendForSuspendingPackage(
                    PLATFORM_PACKAGE_NAME, userId);
        }
        synchronized (getLockObject()) {
            getUserData(userId).mAppsSuspended = suspended;
            saveSettingsLocked(userId);
        }
    }
    private void suspendPersonalAppsInPackageManager(int userId) {
        mInjector.binderWithCleanCallingIdentity(() -> {
            try {
                final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId);
                final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
                        appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
                if (!ArrayUtils.isEmpty(failedPackages)) {
                    Slog.wtf(LOG_TAG, String.format("Failed to %s packages: %s",
                            suspended ? "suspend" : "unsuspend", String.join(",", failedPackages)));
                final String[] failedApps = mIPackageManager.setPackagesSuspendedAsUser(
                        appsToSuspend, true, null, null, null, PLATFORM_PACKAGE_NAME, userId);
                if (!ArrayUtils.isEmpty(failedApps)) {
                    Slog.wtf(LOG_TAG, "Failed to suspend apps: " + String.join(",", failedApps));
                }
            } catch (RemoteException re) {
                // Shouldn't happen.
                Slog.e(LOG_TAG, "Failed talking to the package manager", re);
            }
        });
        synchronized (getLockObject()) {
            getUserData(userId).mAppsSuspended = suspended;
            saveSettingsLocked(userId);
        }
    }
    @GuardedBy("getLockObject()")
+77 −11
Original line number Diff line number Diff line
@@ -18,17 +18,24 @@ package com.android.server.devicepolicy;
import static android.os.UserHandle.USER_SYSTEM;

import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;

import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -354,8 +361,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
        prepareAdmin1AsDo();
        prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);

        final DevicePolicyManagerServiceTestable dpms;
        dpms = bootDpmsUp();
        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();

        // DO should still be DO since no migration should happen.
        assertTrue(dpms.mOwners.hasDeviceOwner());
@@ -364,13 +370,12 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
    @SmallTest
    public void testCompMigrationAffiliated() throws Exception {
        prepareAdmin1AsDo();
        prepareAdmin1AsPo(COPE_PROFILE_USER_ID);
        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);

        // Secure lock screen is needed for password policy APIs to work.
        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);

        final DevicePolicyManagerServiceTestable dpms;
        dpms = bootDpmsUp();
        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();

        // DO should cease to be DO.
        assertFalse(dpms.mOwners.hasDeviceOwner());
@@ -408,6 +413,66 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
                    dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
                            .getEffectiveRestrictions()
                            .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
            assertEquals("Personal apps suspension wasn't migrated",
                    DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED,
                    dpm.getPersonalAppsSuspendedReasons(admin1));
        });
    }

    @SmallTest
    public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
        prepareAdmin1AsDo();
        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);

        // Pretend some packages are suspended.
        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);

        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();

        verify(getServices().packageManagerInternal, never())
                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);

        sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM);

        // Verify that actual package suspension state is not modified after user start
        verify(getServices().packageManagerInternal, never())
                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
        verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
                any(), anyBoolean(), any(), any(), any(), any(), anyInt());

        final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
        poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);

        runAsCaller(poContext, dpms, dpm -> {
            assertEquals("Personal apps suspension wasn't migrated",
                    DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY,
                    dpm.getPersonalAppsSuspendedReasons(admin1));
        });
    }

    @SmallTest
    public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
        prepareAdmin1AsDo();
        prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);

        // Pretend some packages are suspended.
        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);

        final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();

        // Verify that apps get unsuspended.
        verify(getServices().packageManagerInternal)
                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);

        final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
        poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);

        runAsCaller(poContext, dpms, dpm -> {
            assertEquals("Personal apps weren't unsuspended",
                    DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED,
                    dpm.getPersonalAppsSuspendedReasons(admin1));
        });
    }

@@ -439,22 +504,23 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
                        .getAbsoluteFile());
    }

    private void prepareAdmin1AsPo(int profileUserId) throws Exception {
    private void prepareAdmin1AsPo(int profileUserId, int targetSdk) throws Exception {
        preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package,
                R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID);
                R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID, targetSdk);
    }

    private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception {
        preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package,
                R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID);
                R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID,
                Build.VERSION.SDK_INT);
    }

    private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId,
            int policyXmlResId, int adminAppId) throws Exception {
            int policyXmlResId, int adminAppId, int targetSdk) throws Exception {
        final File profileDir = getServices().addUser(profileUserId, 0,
                UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */);
        setUpPackageManagerForFakeAdmin(
                admin, UserHandle.getUid(profileUserId, adminAppId), admin1);
        setUpPackageManagerForFakeAdmin(admin, UserHandle.getUid(profileUserId, adminAppId),
                /* enabledSetting =*/ null, targetSdk, admin1);
        writeInputStreamToFile(getRawStream(policyXmlResId),
                (new File(profileDir, "device_policies.xml")).getAbsoluteFile());
        writeInputStreamToFile(getRawStream(profileOwnerXmlResId),
Loading