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

Commit faab3ea8 authored by Martynas Petuška's avatar Martynas Petuška
Browse files

wipeData/wipeDevice API!

Bug: 242193913
Test: Trigger wipeData via TestDPC

Change-Id: I1f03fe90b960e8a464aee0041d2514894cdb8495
parent a98c5172
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -7669,6 +7669,7 @@ package android.app.admin {
    method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
    method public void wipeData(int);
    method public void wipeData(int, @NonNull CharSequence);
    method public void wipeDevice(int);
    field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
    field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
    field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+70 −38
Original line number Diff line number Diff line
@@ -171,6 +171,7 @@ public class DevicePolicyManager {
    private final boolean mParentInstance;
    private final DevicePolicyResourcesManager mResourcesManager;
    /** @hide */
    public DevicePolicyManager(Context context, IDevicePolicyManager service) {
        this(context, service, false);
@@ -6207,46 +6208,46 @@ public class DevicePolicyManager {
    public static final int WIPE_SILENTLY = 0x0008;
    /**
     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
     * other users will remain unaffected. Calling from the primary user will cause the device to
     * reboot, erasing all device data - including all the secondary users and their data - while
     * booting up.
     * <p>
     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
     * be able to call this method; if it has not, a security exception will be thrown.
     *
     * If the caller is a profile owner of an organization-owned managed profile, it may
     * additionally call this method on the parent instance.
     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
     * entire device, while calling it on the current profile instance would relinquish the device
     * for personal use, removing the managed profile and all policies set by the profile owner.
     * See {@link #wipeData(int, CharSequence)}
     *
     * @param flags Bit mask of additional options: currently supported flags are
     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
     * @throws SecurityException if the calling application does not own an active administrator
     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
     * @throws SecurityException     if the calling application does not own an active
     *                               administrator
     *                               that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is
     *                               not granted the
     *                               {@link android.Manifest.permission#MASTER_CLEAR} permission.
     * @throws IllegalStateException if called on last full-user or system-user
     * @see #wipeDevice(int)
     * @see #wipeData(int, CharSequence)
     */
    public void wipeData(int flags) {
        wipeDataInternal(flags, "");
        wipeDataInternal(flags,
                /* wipeReasonForUser= */ "",
                /* factoryReset= */ false);
    }
    /**
     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
     * other users will remain unaffected, the provided reason for wiping data can be shown to
     * user. Calling from the primary user will cause the device to reboot, erasing all device data
     * - including all the secondary users and their data - while booting up. In this case, we don't
     * show the reason to the user since the device would be factory reset.
     * Ask that all user data be wiped.
     *
     * <p>
     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
     * be able to call this method; if it has not, a security exception will be thrown.
     * If called as a secondary user or managed profile, the user itself and its associated user
     * data will be wiped. In particular, If the caller is a profile owner of an
     * organization-owned managed profile, calling this method will relinquish the device for
     * personal use, removing the managed profile and all policies set by the profile owner.
     * </p>
     *
     * If the caller is a profile owner of an organization-owned managed profile, it may
     * additionally call this method on the parent instance.
     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
     * entire device, while calling it on the current profile instance would relinquish the device
     * for personal use, removing the managed profile and all policies set by the profile owner.
     * <p>
     * Calling this method from the primary user will only work if the calling app is targeting
     * Android 13 or below, in which case it will cause the device to reboot, erasing all device
     * data - including all the secondary users and their data - while booting up. If an app
     * targeting Android 13+ is calling this method from the primary user or last full user,
     * {@link IllegalStateException} will be thrown.
     * </p>
     *
     * If an app wants to wipe the entire device irrespective of which user they are from, they
     * should use {@link #wipeDevice} instead.
     *
     * @param flags Bit mask of additional options: currently supported flags are
     *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -6254,30 +6255,61 @@ public class DevicePolicyManager {
     * @param reason a string that contains the reason for wiping data, which can be
     *            presented to the user.
     * @throws SecurityException if the calling application does not own an active administrator
     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not granted the
     *            {@link android.Manifest.permission#MASTER_CLEAR} permission.
     * @throws IllegalArgumentException if the input reason string is null or empty, or if
     *            {@link #WIPE_SILENTLY} is set.
     * @throws IllegalStateException if called on last full-user or system-user
     * @see #wipeDevice(int)
     * @see #wipeData(int)
     */
    public void wipeData(int flags, @NonNull CharSequence reason) {
        Objects.requireNonNull(reason, "reason string is null");
        Preconditions.checkStringNotEmpty(reason, "reason string is empty");
        Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
        wipeDataInternal(flags, reason.toString());
        wipeDataInternal(flags, reason.toString(), /* factoryReset= */ false);
    }
    /**
     * Internal function for both {@link #wipeData(int)} and
     * {@link #wipeData(int, CharSequence)} to call.
     * Ask that the device be wiped and factory reset.
     *
     * <p>
     * The calling Device Owner or Organization Owned Profile Owner must have requested
     * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call this method; if it has
     * not, a security exception will be thrown.
     *
     * @param flags Bit mask of additional options: currently supported flags are
     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
     * @throws SecurityException if the calling application does not own an active administrator
     *                           that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not
     *                           granted the {@link android.Manifest.permission#MASTER_CLEAR}
     *                           permission.
     * @see #wipeData(int)
     * @see #wipeData(int, CharSequence)
     */
    // TODO(b/255323293) Add host-side tests
    public void wipeDevice(int flags) {
        wipeDataInternal(flags,
                /* wipeReasonForUser= */ "",
                /* factoryReset= */ true);
    }
    /**
     * Internal function for {@link #wipeData(int)}, {@link #wipeData(int, CharSequence)}
     * and {@link #wipeDevice(int)} to call.
     *
     * @hide
     * @see #wipeData(int)
     * @see #wipeData(int, CharSequence)
     * @see #wipeDevice(int)
     */
    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser,
            boolean factoryReset) {
        if (mService != null) {
            try {
                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance);
                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance,
                        factoryReset);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
@@ -8642,7 +8674,7 @@ public class DevicePolicyManager {
    public void reportFailedPasswordAttempt(int userHandle) {
        if (mService != null) {
            try {
                mService.reportFailedPasswordAttempt(userHandle);
                mService.reportFailedPasswordAttempt(userHandle, mParentInstance);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
+5 −2
Original line number Diff line number Diff line
@@ -117,7 +117,10 @@ interface IDevicePolicyManager {

    void lockNow(int flags, boolean parent);

    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
    /**
    * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
    **/
    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent, boolean factoryReset);

    void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
    FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
@@ -161,7 +164,7 @@ interface IDevicePolicyManager {
    boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);

    void reportPasswordChanged(in PasswordMetrics metrics, int userId);
    void reportFailedPasswordAttempt(int userHandle);
    void reportFailedPasswordAttempt(int userHandle, boolean parent);
    void reportSuccessfulPasswordAttempt(int userHandle);
    void reportFailedBiometricAttempt(int userHandle);
    void reportSuccessfulBiometricAttempt(int userHandle);
+59 −11
Original line number Diff line number Diff line
@@ -646,6 +646,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
    private static final long USE_SET_LOCATION_ENABLED = 117835097L;
    /**
     * Forces wipeDataNoLock to attempt removing the user or throw an error as
     * opposed to trying to factory reset the device first and only then falling back to user
     * removal.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
    // Only add to the end of the list. Do not change or rearrange these values, that will break
    // historical data. Do not use negative numbers or zero, logger only handles positive
    // integers.
@@ -6699,8 +6708,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    }
    @Override
    public void wipeDataWithReason(int flags, String wipeReasonForUser,
            boolean calledOnParentInstance) {
    public void wipeDataWithReason(int flags, @NonNull String wipeReasonForUser,
            boolean calledOnParentInstance, boolean factoryReset) {
        if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
            return;
        }
@@ -6782,7 +6791,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                "DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
                adminName, calledByProfileOwnerOnOrgOwnedDevice);
        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
                calledOnParentInstance, factoryReset);
    }
    private String getGenericWipeReason(
@@ -6844,8 +6854,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
    }
    /**
     * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
     *                     factory reset
     */
    private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
                                String wipeReasonForUser, int userId) {
            @NonNull String wipeReasonForUser, int userId, boolean calledOnParentInstance,
            @Nullable Boolean factoryReset) {
        wtfIfInLock();
        mInjector.binderWithCleanCallingIdentity(() -> {
@@ -6863,7 +6878,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                        + " restriction is set for user " + userId);
            }
            if (userId == UserHandle.USER_SYSTEM) {
            boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
            boolean wipeDevice;
            if (factoryReset == null || !CompatChanges.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR)) {
                // Legacy mode
                wipeDevice = isSystemUser;
            } else {
                // Explicit behaviour
                if (factoryReset) {
                    // TODO(b/254031494) Replace with new factory reset permission checks
                    boolean hasPermission = isDeviceOwnerUserId(userId)
                            || (isOrganizationOwnedDeviceWithManagedProfile()
                            && calledOnParentInstance);
                    Preconditions.checkState(hasPermission,
                            "Admin %s does not have permission to factory reset the device.",
                            userId);
                    wipeDevice = true;
                } else {
                    Preconditions.checkCallAuthorization(!isSystemUser,
                            "User %s is a system user and cannot be removed", userId);
                    boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
                            && mUserManager.getAliveUsers().stream()
                            .filter((it) -> it.getUserHandle().getIdentifier() != userId)
                            .noneMatch(UserInfo::isFull);
                    Preconditions.checkState(!isLastNonHeadlessUser,
                            "Removing user %s would leave the device without any active users. "
                                    + "Consider factory resetting the device instead.",
                            userId);
                    wipeDevice = false;
                }
            }
            if (wipeDevice) {
                forceWipeDeviceNoLock(
                        (flags & WIPE_EXTERNAL_STORAGE) != 0,
                        internalReason,
@@ -7131,7 +7176,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    }
    @Override
    public void reportFailedPasswordAttempt(int userHandle) {
    public void reportFailedPasswordAttempt(int userHandle, boolean parent) {
        Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
        final CallerIdentity caller = getCallerIdentity();
@@ -7153,7 +7198,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                saveSettingsLocked(userHandle);
                if (mHasFeature) {
                    strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                            userHandle, /* parent */ false);
                            userHandle, /* parent= */ false);
                    int max = strictestAdmin != null
                            ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                    if (max > 0 && policy.mFailedPasswordAttempts >= max) {
@@ -7186,7 +7231,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                        /* flags= */ 0,
                        /* reason= */ "reportFailedPasswordAttempt()",
                        getFailedPasswordAttemptWipeMessage(),
                        userId);
                        userId,
                        /* calledOnParentInstance= */ parent,
                        // factoryReset=null to enable U- behaviour
                        /* factoryReset= */ null);
            } catch (SecurityException e) {
                Slogf.w(LOG_TAG, "Failed to wipe user " + userId
                        + " after max failed password attempts reached.", e);
@@ -7195,7 +7243,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        if (mInjector.securityLogIsLoggingEnabled()) {
            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
                    /*result*/ 0, /*method strength*/ 1);
                    /* result= */ 0, /* method strength= */ 1);
        }
    }
+8 −8
Original line number Diff line number Diff line
@@ -140,7 +140,7 @@ import android.security.KeyChain;
import android.security.keystore.AttestationUtils;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth
import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -5087,7 +5087,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
    }

    @Test
    public void testWipeDataDeviceOwner() throws Exception {
    public void testWipeDevice_DeviceOwner() throws Exception {
        setDeviceOwner();
        when(getServices().userManager.getUserRestrictionSource(
                UserManager.DISALLOW_FACTORY_RESET,
@@ -5096,7 +5096,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                thenReturn("Just a test string.");

        dpm.wipeData(0);
        dpm.wipeDevice(0);

        verifyRebootWipeUserData(/* wipeEuicc= */ false);
    }
@@ -5111,13 +5111,13 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                thenReturn("Just a test string.");

        dpm.wipeData(WIPE_EUICC);
        dpm.wipeDevice(WIPE_EUICC);

        verifyRebootWipeUserData(/* wipeEuicc= */ true);
    }

    @Test
    public void testWipeDataDeviceOwnerDisallowed() throws Exception {
    public void testWipeDevice_DeviceOwnerDisallowed() throws Exception {
        setDeviceOwner();
        when(getServices().userManager.getUserRestrictionSource(
                UserManager.DISALLOW_FACTORY_RESET,
@@ -5128,7 +5128,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        // The DO is not allowed to wipe the device if the user restriction was set
        // by the system
        assertExpectException(SecurityException.class, /* messageRegex= */ null,
                () -> dpm.wipeData(0));
                () -> dpm.wipeDevice(0));
    }

    @Test
@@ -7986,7 +7986,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
    }

    @Test
    public void testWipeData_financeDo_success() throws Exception {
    public void testWipeDevice_financeDo_success() throws Exception {
        setDeviceOwner();
        dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
        when(getServices().userManager.getUserRestrictionSource(
@@ -7997,7 +7997,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
                .getString(R.string.work_profile_deleted_description_dpm_wipe))
                .thenReturn("Test string");

        dpm.wipeData(0);
        dpm.wipeDevice(0);

        verifyRebootWipeUserData(/* wipeEuicc= */ false);
    }