Persist delayed calls to factory reset...
...so the device is factory reset on reboot if necessary.
NOTE: this is same as 23bcae6f5bd8c08d6315dbaba7b9ab34f1c1bac9, but
without the extra, accidental parser.next() line that broke stuff.
Test: atest FrameworksServicesTests:DevicePolicyManagerTest
Test: atest FactoryResetterTest
Test: manual verification
Bug: 171603586
Bug: 177391800
Change-Id: Ic18ee40b04eaf68c9da278970c9586cca93eef9d
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 5e7f984..ba3ae45 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
@@ -24,6 +25,7 @@
import android.os.PersistableBundle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.TypedXmlPullParser;
@@ -85,6 +87,15 @@
static final String NEW_USER_DISCLAIMER_NOT_NEEDED = "not_needed";
static final String NEW_USER_DISCLAIMER_NEEDED = "needed";
+ private static final String ATTR_FACTORY_RESET_FLAGS = "factory-reset-flags";
+ private static final String ATTR_FACTORY_RESET_REASON = "factory-reset-reason";
+
+ // NOTE: must be public because of DebugUtils.flagsToString()
+ public static final int FACTORY_RESET_FLAG_ON_BOOT = 1;
+ public static final int FACTORY_RESET_FLAG_WIPE_EXTERNAL_STORAGE = 2;
+ public static final int FACTORY_RESET_FLAG_WIPE_EUICC = 4;
+ public static final int FACTORY_RESET_FLAG_WIPE_FACTORY_RESET_PROTECTION = 8;
+
private static final String TAG = DevicePolicyManagerService.LOG_TAG;
private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
@@ -99,6 +110,9 @@
int mUserProvisioningState;
int mPermissionPolicy;
+ int mFactoryResetFlags;
+ String mFactoryResetReason;
+
boolean mDeviceProvisioningConfigApplied = false;
final ArrayMap<ComponentName, ActiveAdmin> mAdminMap = new ArrayMap<>();
@@ -200,6 +214,17 @@
out.attribute(null, ATTR_NEW_USER_DISCLAIMER, policyData.mNewUserDisclaimer);
}
+ if (policyData.mFactoryResetFlags != 0) {
+ if (VERBOSE_LOG) {
+ Slog.v(TAG, "Storing factory reset flags for user " + policyData.mUserId + ": "
+ + factoryResetFlagsToString(policyData.mFactoryResetFlags));
+ }
+ out.attributeInt(null, ATTR_FACTORY_RESET_FLAGS, policyData.mFactoryResetFlags);
+ }
+ if (policyData.mFactoryResetReason != null) {
+ out.attribute(null, ATTR_FACTORY_RESET_REASON, policyData.mFactoryResetReason);
+ }
+
// Serialize delegations.
for (int i = 0; i < policyData.mDelegationMap.size(); ++i) {
final String delegatePackage = policyData.mDelegationMap.keyAt(i);
@@ -427,6 +452,13 @@
}
policy.mNewUserDisclaimer = parser.getAttributeValue(null, ATTR_NEW_USER_DISCLAIMER);
+ policy.mFactoryResetFlags = parser.getAttributeInt(null, ATTR_FACTORY_RESET_FLAGS, 0);
+ if (VERBOSE_LOG) {
+ Slog.v(TAG, "Restored factory reset flags for user " + policy.mUserId + ": "
+ + factoryResetFlagsToString(policy.mFactoryResetFlags));
+ }
+ policy.mFactoryResetReason = parser.getAttributeValue(null, ATTR_FACTORY_RESET_REASON);
+
int outerDepth = parser.getDepth();
policy.mLockTaskPackages.clear();
policy.mAdminList.clear();
@@ -572,6 +604,22 @@
}
}
+ void setDelayedFactoryReset(@NonNull String reason, boolean wipeExtRequested, boolean wipeEuicc,
+ boolean wipeResetProtectionData) {
+ mFactoryResetReason = reason;
+
+ mFactoryResetFlags = FACTORY_RESET_FLAG_ON_BOOT;
+ if (wipeExtRequested) {
+ mFactoryResetFlags |= FACTORY_RESET_FLAG_WIPE_EXTERNAL_STORAGE;
+ }
+ if (wipeEuicc) {
+ mFactoryResetFlags |= FACTORY_RESET_FLAG_WIPE_EUICC;
+ }
+ if (wipeResetProtectionData) {
+ mFactoryResetFlags |= FACTORY_RESET_FLAG_WIPE_FACTORY_RESET_PROTECTION;
+ }
+ }
+
void dump(IndentingPrintWriter pw) {
pw.println();
pw.println("Enabled Device Admins (User " + mUserId + ", provisioningState: "
@@ -603,6 +651,19 @@
pw.print("mUserSetupComplete="); pw.println(mUserSetupComplete);
pw.print("mAffiliationIds="); pw.println(mAffiliationIds);
pw.print("mNewUserDisclaimer="); pw.println(mNewUserDisclaimer);
+ if (mFactoryResetFlags != 0) {
+ pw.print("mFactoryResetFlags="); pw.print(mFactoryResetFlags);
+ pw.print(" (");
+ pw.print(factoryResetFlagsToString(mFactoryResetFlags));
+ pw.println(')');
+ }
+ if (mFactoryResetReason != null) {
+ pw.print("mFactoryResetReason="); pw.println(mFactoryResetReason);
+ }
pw.decreaseIndent();
}
+
+ static String factoryResetFlagsToString(int flags) {
+ return DebugUtils.flagsToString(DevicePolicyData.class, "FACTORY_RESET_FLAG_", flags);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8b575fb..83c5870 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1317,12 +1317,11 @@
mContext.getSystemService(PowerManager.class).reboot(reason);
}
- void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
boolean wipeEuicc, boolean wipeExtRequested, boolean wipeResetProtectionData)
throws IOException {
- FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker)
- .setReason(reason).setShutdown(shutdown)
- .setForce(force).setWipeEuicc(wipeEuicc)
+ return FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker)
+ .setReason(reason).setShutdown(shutdown).setForce(force).setWipeEuicc(wipeEuicc)
.setWipeAdoptableStorage(wipeExtRequested)
.setWipeFactoryResetProtection(wipeResetProtectionData)
.build().factoryReset();
@@ -2784,6 +2783,10 @@
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
case SystemService.PHASE_BOOT_COMPLETED:
+ // Ideally it should be done earlier, but currently it relies on RecoverySystem,
+ // which would hang on earlier phases
+ factoryResetIfDelayedEarlier();
+
ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
break;
}
@@ -6217,10 +6220,24 @@
boolean wipeResetProtectionData) {
wtfIfInLock();
boolean success = false;
+
try {
- mInjector.recoverySystemRebootWipeUserData(
+ boolean delayed = !mInjector.recoverySystemRebootWipeUserData(
/* shutdown= */ false, reason, /* force= */ true, /* wipeEuicc= */ wipeEuicc,
wipeExtRequested, wipeResetProtectionData);
+ if (delayed) {
+ // Persist the request so the device is automatically factory-reset on next start if
+ // the system crashes or reboots before the {@code DevicePolicySafetyChecker} calls
+ // its callback.
+ Slog.i(LOG_TAG, String.format("Persisting factory reset request as it could be "
+ + "delayed by %s", mSafetyChecker));
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
+ policy.setDelayedFactoryReset(reason, wipeExtRequested, wipeEuicc,
+ wipeResetProtectionData);
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ }
success = true;
} catch (IOException | SecurityException e) {
Slog.w(LOG_TAG, "Failed requesting data wipe", e);
@@ -6229,6 +6246,40 @@
}
}
+ private void factoryResetIfDelayedEarlier() {
+ synchronized (getLockObject()) {
+ DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
+
+ if (policy.mFactoryResetFlags == 0) return;
+
+ if (policy.mFactoryResetReason == null) {
+ // Shouldn't happen.
+ Slog.e(LOG_TAG, "no persisted reason for factory resetting");
+ policy.mFactoryResetReason = "requested before boot";
+ }
+ FactoryResetter factoryResetter = FactoryResetter.newBuilder(mContext)
+ .setReason(policy.mFactoryResetReason).setForce(true)
+ .setWipeEuicc((policy.mFactoryResetFlags & DevicePolicyData
+ .FACTORY_RESET_FLAG_WIPE_EUICC) != 0)
+ .setWipeAdoptableStorage((policy.mFactoryResetFlags & DevicePolicyData
+ .FACTORY_RESET_FLAG_WIPE_EXTERNAL_STORAGE) != 0)
+ .setWipeFactoryResetProtection((policy.mFactoryResetFlags & DevicePolicyData
+ .FACTORY_RESET_FLAG_WIPE_FACTORY_RESET_PROTECTION) != 0)
+ .build();
+ Slog.i(LOG_TAG, "Factory resetting on boot using " + factoryResetter);
+ try {
+ if (!factoryResetter.factoryReset()) {
+ // Shouldn't happen because FactoryResetter was created without a
+ // DevicePolicySafetyChecker.
+ Slog.wtf(LOG_TAG, "Factory reset using " + factoryResetter + " failed.");
+ }
+ } catch (IOException e) {
+ // Shouldn't happen.
+ Slog.wtf(LOG_TAG, "Could not factory reset using " + factoryResetter, e);
+ }
+ }
+ }
+
private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
boolean success = false;
try {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
index 564950b..457255b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
@@ -52,14 +52,17 @@
/**
* Factory reset the device according to the builder's arguments.
+ *
+ * @return {@code true} if device was factory reset, or {@code false} if it was delayed by the
+ * {@link DevicePolicySafetyChecker}.
*/
- public void factoryReset() throws IOException {
+ public boolean factoryReset() throws IOException {
Preconditions.checkCallAuthorization(mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MASTER_CLEAR) == PackageManager.PERMISSION_GRANTED);
if (mSafetyChecker == null) {
factoryResetInternalUnchecked();
- return;
+ return true;
}
IResultReceiver receiver = new IResultReceiver.Stub() {
@@ -77,6 +80,36 @@
};
Slog.i(TAG, String.format("Delaying factory reset until %s confirms", mSafetyChecker));
mSafetyChecker.onFactoryReset(receiver);
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("FactoryResetter[");
+ if (mReason == null) {
+ builder.append("no_reason");
+ } else {
+ builder.append("reason='").append(mReason).append("'");
+ }
+ if (mSafetyChecker != null) {
+ builder.append(",hasSafetyChecker");
+ }
+ if (mShutdown) {
+ builder.append(",shutdown");
+ }
+ if (mForce) {
+ builder.append(",force");
+ }
+ if (mWipeEuicc) {
+ builder.append(",wipeEuicc");
+ }
+ if (mWipeAdoptableStorage) {
+ builder.append(",wipeAdoptableStorage");
+ }
+ if (mWipeFactoryResetProtection) {
+ builder.append(",ipeFactoryResetProtection");
+ }
+ return builder.append(']').toString();
}
private void factoryResetInternalUnchecked() throws IOException {
diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
index c4d14f9..589a349 100644
--- a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
@@ -20,6 +20,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
@@ -158,10 +160,11 @@
public void testFactoryReset_storageOnly() throws Exception {
allowFactoryReset();
- FactoryResetter.newBuilder(mContext)
+ boolean success = FactoryResetter.newBuilder(mContext)
.setWipeAdoptableStorage(true).build()
.factoryReset();
+ assertThat(success).isTrue();
verifyWipeAdoptableStorageCalled();
verifyWipeFactoryResetProtectionNotCalled();
verifyRebootWipeUserDataMinimumArgsCalled();
@@ -171,10 +174,11 @@
public void testFactoryReset_frpOnly() throws Exception {
allowFactoryReset();
- FactoryResetter.newBuilder(mContext)
+ boolean success = FactoryResetter.newBuilder(mContext)
.setWipeFactoryResetProtection(true)
.build().factoryReset();
+ assertThat(success).isTrue();
verifyWipeAdoptableStorageNotCalled();
verifyWipeFactoryResetProtectionCalled();
verifyRebootWipeUserDataMinimumArgsCalled();
@@ -184,7 +188,7 @@
public void testFactoryReset_allArgs() throws Exception {
allowFactoryReset();
- FactoryResetter.newBuilder(mContext)
+ boolean success = FactoryResetter.newBuilder(mContext)
.setReason(REASON)
.setForce(true)
.setShutdown(true)
@@ -193,6 +197,7 @@
.setWipeFactoryResetProtection(true)
.build().factoryReset();
+ assertThat(success).isTrue();
verifyWipeAdoptableStorageCalled();
verifyWipeFactoryResetProtectionCalled();
verifyRebootWipeUserDataAllArgsCalled();
@@ -202,9 +207,10 @@
public void testFactoryReset_minimumArgs_safetyChecker_neverReplied() throws Exception {
allowFactoryReset();
- FactoryResetter.newBuilder(mContext).setSafetyChecker(mSafetyChecker).build()
- .factoryReset();
+ boolean success = FactoryResetter.newBuilder(mContext)
+ .setSafetyChecker(mSafetyChecker).build().factoryReset();
+ assertThat(success).isFalse();
verifyWipeAdoptableStorageNotCalled();
verifyWipeFactoryResetProtectionNotCalled();
verifyRebootWipeUserDataNotCalled();
@@ -221,7 +227,7 @@
return null;
}).when(mSafetyChecker).onFactoryReset(any());
- FactoryResetter.newBuilder(mContext)
+ boolean success = FactoryResetter.newBuilder(mContext)
.setSafetyChecker(mSafetyChecker)
.setReason(REASON)
.setForce(true)
@@ -231,6 +237,7 @@
.setWipeFactoryResetProtection(true)
.build().factoryReset();
+ assertThat(success).isFalse();
verifyWipeAdoptableStorageCalled();
verifyWipeFactoryResetProtectionCalled();
verifyRebootWipeUserDataAllArgsCalled();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9c28c99..f3576af 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -319,10 +319,10 @@
}
@Override
- void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
boolean wipeEuicc, boolean wipeExtRequested, boolean wipeResetProtectionData)
throws IOException {
- services.recoverySystem.rebootWipeUserData(shutdown, reason, force, wipeEuicc,
+ return services.recoverySystem.rebootWipeUserData(shutdown, reason, force, wipeEuicc,
wipeExtRequested, wipeResetProtectionData);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 7d1de86..d8e0c5c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -392,9 +392,10 @@
}
public static class RecoverySystemForMock {
- public void rebootWipeUserData(boolean shutdown, String reason, boolean force,
+ public boolean rebootWipeUserData(boolean shutdown, String reason, boolean force,
boolean wipeEuicc, boolean wipeExtRequested, boolean wipeResetProtectionData)
throws IOException {
+ return false;
}
}