Merge "Add flag to wipe eUICC data" into oc-dr1-dev
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9eacd21..ea0829f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,7 +55,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract.Directory;
-import android.provider.Settings;
import android.security.Credentials;
import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
@@ -3134,6 +3133,14 @@
public static final int WIPE_RESET_PROTECTION_DATA = 0x0002;
/**
+ * Flag for {@link #wipeData(int)}: also erase the device's eUICC data.
+ *
+ * TODO(b/35851809): make this public.
+ * @hide
+ */
+ public static final int WIPE_EUICC = 0x0004;
+
+ /**
* 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
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index db9f28b..6f458e0 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -22,20 +22,26 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
+import com.android.internal.logging.MetricsLogger;
+
import libcore.io.Streams;
-import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -46,22 +52,19 @@
import java.io.RandomAccessFile;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
-import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
-import com.android.internal.logging.MetricsLogger;
-
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -84,11 +87,19 @@
/** Send progress to listeners no more often than this (in ms). */
private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
+ private static final long DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS = 30000L; // 30 s
+
+ private static final long MIN_EUICC_WIPING_TIMEOUT_MILLIS = 5000L; // 5 s
+
+ private static final long MAX_EUICC_WIPING_TIMEOUT_MILLIS = 60000L; // 60 s
+
/** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
private static final File RECOVERY_DIR = new File("/cache/recovery");
private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
private static final File LAST_INSTALL_FILE = new File(RECOVERY_DIR, "last_install");
private static final String LAST_PREFIX = "last_";
+ private static final String ACTION_WIPE_EUICC_DATA =
+ "com.android.internal.action.WIPE_EUICC_DATA";
/**
* The recovery image uses this file to identify the location (i.e. blocks)
@@ -673,18 +684,26 @@
*/
public static void rebootWipeUserData(Context context) throws IOException {
rebootWipeUserData(context, false /* shutdown */, context.getPackageName(),
- false /* force */);
+ false /* force */, false /* wipeEuicc */);
}
/** {@hide} */
public static void rebootWipeUserData(Context context, String reason) throws IOException {
- rebootWipeUserData(context, false /* shutdown */, reason, false /* force */);
+ rebootWipeUserData(context, false /* shutdown */, reason, false /* force */,
+ false /* wipeEuicc */);
}
/** {@hide} */
public static void rebootWipeUserData(Context context, boolean shutdown)
throws IOException {
- rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */);
+ rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */,
+ false /* wipeEuicc */);
+ }
+
+ /** {@hide} */
+ public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
+ boolean force) throws IOException {
+ rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */);
}
/**
@@ -701,6 +720,7 @@
* @param reason the reason for the wipe that is visible in the logs
* @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction
* should be ignored
+ * @param wipeEuicc whether wipe the euicc data
*
* @throws IOException if writing the recovery command file
* fails, or if the reboot itself fails.
@@ -709,7 +729,7 @@
* @hide
*/
public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
- boolean force) throws IOException {
+ boolean force, boolean wipeEuicc) throws IOException {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
throw new SecurityException("Wiping data is not allowed for this user.");
@@ -731,6 +751,10 @@
// Block until the ordered broadcast has completed.
condition.block();
+ if (wipeEuicc) {
+ wipeEuiccData(context);
+ }
+
String shutdownArg = null;
if (shutdown) {
shutdownArg = "--shutdown_after";
@@ -745,6 +769,61 @@
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
+ private static void wipeEuiccData(Context context) {
+ EuiccManager euiccManager = (EuiccManager) context.getSystemService(
+ Context.EUICC_SERVICE);
+ if (euiccManager != null && euiccManager.isEnabled()) {
+ CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1);
+
+ BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_WIPE_EUICC_DATA.equals(intent.getAction())) {
+ if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+ int detailedCode = intent.getIntExtra(
+ EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
+ Log.e(TAG, "Error wiping euicc data, Detailed code = "
+ + detailedCode);
+ } else {
+ Log.d(TAG, "Successfully wiped euicc data.");
+ }
+ euiccFactoryResetLatch.countDown();
+ }
+ }
+ };
+
+ Intent intent = new Intent(ACTION_WIPE_EUICC_DATA);
+ intent.setPackage("android");
+ PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
+ IntentFilter filterConsent = new IntentFilter();
+ filterConsent.addAction(ACTION_WIPE_EUICC_DATA);
+ HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
+ euiccHandlerThread.start();
+ Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
+ context.registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler);
+ euiccManager.eraseSubscriptions(callbackIntent);
+ try {
+ long waitingTimeMillis = Settings.Global.getLong(
+ context.getContentResolver(),
+ Settings.Global.EUICC_WIPING_TIMEOUT_MILLIS,
+ DEFAULT_EUICC_WIPING_TIMEOUT_MILLIS);
+ if (waitingTimeMillis < MIN_EUICC_WIPING_TIMEOUT_MILLIS) {
+ waitingTimeMillis = MIN_EUICC_WIPING_TIMEOUT_MILLIS;
+ } else if (waitingTimeMillis > MAX_EUICC_WIPING_TIMEOUT_MILLIS) {
+ waitingTimeMillis = MAX_EUICC_WIPING_TIMEOUT_MILLIS;
+ }
+ if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
+ Log.e(TAG, "Timeout wiping eUICC data.");
+ }
+ context.unregisterReceiver(euiccWipeFinishReceiver);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.e(TAG, "Wiping eUICC data interrupted", e);
+ }
+ }
+ }
+
/** {@hide} */
public static void rebootPromptAndWipeUserData(Context context, String reason)
throws IOException {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b4c5025..b72f8c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -302,6 +302,7 @@
<protected-broadcast android:name="com.android.server.WifiManager.action.DEVICE_IDLE" />
<protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" />
<protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" />
+ <protected-broadcast android:name="com.android.server.action.WIPE_EUICC_DATA" />
<protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
<protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
<protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 136d335..8cac6e0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -43,6 +43,7 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
@@ -1691,9 +1692,9 @@
mContext.getSystemService(PowerManager.class).reboot(reason);
}
- void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
- throws IOException {
- RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force);
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean wipeEuicc) throws IOException {
+ RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force, wipeEuicc);
}
boolean systemPropertiesGetBoolean(String key, boolean def) {
@@ -5302,7 +5303,7 @@
}
}
- private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) {
+ private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) {
wtfIfInLock();
if (wipeExtRequested) {
@@ -5312,7 +5313,7 @@
}
try {
mInjector.recoverySystemRebootWipeUserData(
- /*shutdown=*/ false, reason, /*force=*/ true);
+ /*shutdown=*/ false, reason, /*force=*/ true, /*wipeEuicc=*/ wipeEuicc);
} catch (IOException | SecurityException e) {
Slog.w(LOG_TAG, "Failed requesting data wipe", e);
}
@@ -5389,7 +5390,7 @@
// removes that user (but still clears FRP...)
if (userId == UserHandle.USER_SYSTEM) {
forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
- reason);
+ reason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
} else {
forceWipeUser(userId);
}
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 a33153e..5471715 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -269,9 +269,9 @@
}
@Override
- void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
- throws IOException {
- services.recoverySystem.rebootWipeUserData(shutdown, reason, force);
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean wipeEuicc) throws IOException {
+ services.recoverySystem.rebootWipeUserData(shutdown, reason, force, wipeEuicc);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 6393b0b..c58b733 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -17,6 +17,7 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
@@ -3451,7 +3452,21 @@
dpm.wipeData(0);
verify(getServices().recoverySystem).rebootWipeUserData(
- /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true),
+ /*wipeEuicc=*/ eq(false));
+ }
+
+ public void testWipeEuiccDataEnabled() throws Exception {
+ setDeviceOwner();
+ when(getServices().userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ dpm.wipeData(WIPE_EUICC);
+ verify(getServices().recoverySystem).rebootWipeUserData(
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true),
+ /*wipeEuicc=*/ eq(true));
}
public void testWipeDataDeviceOwnerDisallowed() throws Exception {
@@ -3549,7 +3564,8 @@
// The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the
// user restriction and the policy were set by the DO.
verify(getServices().recoverySystem).rebootWipeUserData(
- /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true),
+ /*wipeEuicc=*/ eq(false));
}
public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception {
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 ed8de05..8121bcf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -314,8 +314,8 @@
}
public static class RecoverySystemForMock {
- public void rebootWipeUserData(
- boolean shutdown, String reason, boolean force) throws IOException {
+ public void rebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean wipeEuicc) throws IOException {
}
}