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

Commit c6ca8d1c authored by Felipe Leme's avatar Felipe Leme
Browse files

Integrated FactoryResetter with DevicePolicySafetyChecker.

Test: atest FrameworksMockingServicesTests:FactoryResetterTest \
            CtsDevicePolicyManagerTestCases:DeviceOwnerTest#testPolicySafetyCheckerIntegration

Bug: 171603586

Change-Id: Ib046d168c3380bbe4a3923530860ba0acb8fb6ae
parent b8498836
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.app.admin;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;

import com.android.internal.os.IResultReceiver;

/**
 * Interface responsible to check if a {@link DevicePolicyManager} API can be safely executed.
 *
@@ -28,9 +30,7 @@ public interface DevicePolicySafetyChecker {
    /**
     * Returns whether the given {@code operation} can be safely executed at the moment.
     */
    default boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
        return true;
    }
    boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation);

    /**
     * Returns a new exception for when the given {@code operation} cannot be safely executed.
@@ -39,4 +39,13 @@ public interface DevicePolicySafetyChecker {
    default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) {
        return new UnsafeStateException(operation);
    }

    /**
     * Called when a request was made to factory reset the device, so it can be delayed if it's not
     * safe to proceed.
     *
     * @param callback callback whose {@code send()} method must be called when it's safe to factory
     * reset.
     */
    void onFactoryReset(IResultReceiver callback);
}
+14 −2
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ import android.os.UserHandle;
 * All parameters are verified on object creation unless the component name is null and the
 * caller is a delegate.
 */
class CallerIdentity {
final class CallerIdentity {

    private final int mUid;
    @Nullable
@@ -66,4 +66,16 @@ class CallerIdentity {
    public boolean hasPackage() {
        return mPackageName != null;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("CallerIdentity[uid=").append(mUid);
        if (mPackageName != null) {
            builder.append(", pkg=").append(mPackageName);
        }
        if (mComponentName != null) {
            builder.append(", cmp=").append(mComponentName.flattenToShortString());
        }
        return builder.append("]").toString();
    }
}
+23 −4
Original line number Diff line number Diff line
@@ -983,8 +983,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    @Override
    public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
        Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker);
        CallerIdentity callerIdentity = getCallerIdentity();
        Preconditions.checkCallAuthorization(mIsAutomotive || isAdb(callerIdentity), "can only set "
                + "DevicePolicySafetyChecker on automotive builds or from ADB (but caller is %s)",
                callerIdentity);
        setDevicePolicySafetyCheckerUnchecked(safetyChecker);
    }
    /**
     * Used by {@code setDevicePolicySafetyChecker()} above and {@link OneTimeSafetyChecker}.
     */
    void setDevicePolicySafetyCheckerUnchecked(DevicePolicySafetyChecker safetyChecker) {
        Slog.i(LOG_TAG, String.format("Setting DevicePolicySafetyChecker as %s", safetyChecker));
        mSafetyChecker = safetyChecker;
        mInjector.setDevicePolicySafetyChecker(safetyChecker);
    }
    /**
@@ -1021,8 +1033,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) {
        Preconditions.checkCallAuthorization(
                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
        Slog.i(LOG_TAG, "setNextOperationSafety(" + DevicePolicyManager.operationToString(operation)
                + ", " + safe + ")");
        Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %b)",
                DevicePolicyManager.operationToString(operation), safe));
        mSafetyChecker = new OneTimeSafetyChecker(this, operation, safe);
    }
@@ -1034,6 +1046,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        public final Context mContext;
        private @Nullable DevicePolicySafetyChecker mSafetyChecker;
        Injector(Context context) {
            mContext = context;
        }
@@ -1278,7 +1292,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
                boolean wipeEuicc, boolean wipeExtRequested, boolean wipeResetProtectionData)
                        throws IOException {
            FactoryResetter.newBuilder(mContext).setReason(reason).setShutdown(shutdown)
            FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker)
                    .setReason(reason).setShutdown(shutdown)
                    .setForce(force).setWipeEuicc(wipeEuicc)
                    .setWipeAdoptableStorage(wipeExtRequested)
                    .setWipeFactoryResetProtection(wipeResetProtectionData)
@@ -1433,6 +1448,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        public boolean isChangeEnabled(long changeId, String packageName, int userId) {
            return CompatChanges.isChangeEnabled(changeId, packageName, UserHandle.of(userId));
        }
        void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
            mSafetyChecker = safetyChecker;
        }
    }
    /**
+48 −10
Original line number Diff line number Diff line
@@ -17,14 +17,18 @@
package com.android.server.devicepolicy;

import android.annotation.Nullable;
import android.app.admin.DevicePolicySafetyChecker;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Log;
import android.util.Slog;

import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;

import java.io.IOException;
@@ -38,6 +42,7 @@ public final class FactoryResetter {
    private static final String TAG = FactoryResetter.class.getSimpleName();

    private final Context mContext;
    private final @Nullable DevicePolicySafetyChecker mSafetyChecker;
    private final @Nullable String mReason;
    private final boolean mShutdown;
    private final boolean mForce;
@@ -45,18 +50,40 @@ public final class FactoryResetter {
    private final boolean mWipeAdoptableStorage;
    private final boolean mWipeFactoryResetProtection;


    /**
     * Factory reset the device according to the builder's arguments.
     */
    public void factoryReset() throws IOException {
        Log.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b"
                + ", wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc,
                mWipeAdoptableStorage, mWipeFactoryResetProtection));

        Preconditions.checkCallAuthorization(mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MASTER_CLEAR) == PackageManager.PERMISSION_GRANTED);

        if (mSafetyChecker == null) {
            factoryResetInternalUnchecked();
            return;
        }

        IResultReceiver receiver = new IResultReceiver.Stub() {
            @Override
            public void send(int resultCode, Bundle resultData) throws RemoteException {
                Slog.i(TAG, String.format("Factory reset confirmed by %s, proceeding",
                        mSafetyChecker));
                try {
                    factoryResetInternalUnchecked();
                } catch (IOException e) {
                    // Shouldn't happen
                    Slog.wtf(TAG, "IOException calling underlying systems", e);
                }
            }
        };
        Slog.i(TAG, String.format("Delaying factory reset until %s confirms", mSafetyChecker));
        mSafetyChecker.onFactoryReset(receiver);
    }

    private void factoryResetInternalUnchecked() throws IOException {
        Slog.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, "
                + "wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc,
                mWipeAdoptableStorage, mWipeFactoryResetProtection));

        UserManager um = mContext.getSystemService(UserManager.class);
        if (!mForce && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
            throw new SecurityException("Factory reset is not allowed for this user.");
@@ -66,15 +93,15 @@ public final class FactoryResetter {
            PersistentDataBlockManager manager = mContext
                    .getSystemService(PersistentDataBlockManager.class);
            if (manager != null) {
                Log.w(TAG, "Wiping factory reset protection");
                Slog.w(TAG, "Wiping factory reset protection");
                manager.wipe();
            } else {
                Log.w(TAG, "No need to wipe factory reset protection");
                Slog.w(TAG, "No need to wipe factory reset protection");
            }
        }

        if (mWipeAdoptableStorage) {
            Log.w(TAG, "Wiping adoptable storage");
            Slog.w(TAG, "Wiping adoptable storage");
            StorageManager sm = mContext.getSystemService(StorageManager.class);
            sm.wipeAdoptableDisks();
        }
@@ -84,8 +111,9 @@ public final class FactoryResetter {

    private FactoryResetter(Builder builder) {
        mContext = builder.mContext;
        mShutdown = builder.mShutdown;
        mSafetyChecker = builder.mSafetyChecker;
        mReason = builder.mReason;
        mShutdown = builder.mShutdown;
        mForce = builder.mForce;
        mWipeEuicc = builder.mWipeEuicc;
        mWipeAdoptableStorage = builder.mWipeAdoptableStorage;
@@ -105,6 +133,7 @@ public final class FactoryResetter {
    public static final class Builder {

        private final Context mContext;
        private @Nullable DevicePolicySafetyChecker mSafetyChecker;
        private @Nullable String mReason;
        private boolean mShutdown;
        private boolean mForce;
@@ -116,6 +145,15 @@ public final class FactoryResetter {
            mContext = Objects.requireNonNull(context);
        }

        /**
         * Sets a {@link DevicePolicySafetyChecker} object that will be used to delay the
         * factory reset when it's not safe to do so.
         */
        public Builder setSafetyChecker(@Nullable DevicePolicySafetyChecker safetyChecker) {
            mSafetyChecker = safetyChecker;
            return this;
        }

        /**
         * Sets the (non-null) reason for the factory reset that is visible in the logs
         */
+8 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicySafetyChecker;
import android.util.Slog;

import com.android.internal.os.IResultReceiver;

import java.util.Objects;

//TODO(b/172376923): add unit tests
@@ -61,7 +63,12 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker {
        }
        Slog.i(TAG, "isDevicePolicyOperationSafe(" + name + "): returning " + safe
                + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
        mService.setDevicePolicySafetyChecker(mRealSafetyChecker);
        mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
        return safe;
    }

    @Override
    public void onFactoryReset(IResultReceiver callback) {
        throw new UnsupportedOperationException();
    }
}
Loading