diff options
7 files changed, 128 insertions, 23 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d0091b6646dd..14162afdb25d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3134,6 +3134,14 @@ public class DevicePolicyManager { 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 db9f28b77288..6f458e084a42 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -22,20 +22,26 @@ import android.annotation.RequiresPermission; 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.InputStream; 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 @@ public class RecoverySystem { /** 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 class RecoverySystem { */ 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 @@ public class RecoverySystem { * @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 @@ public class RecoverySystem { * @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 @@ public class RecoverySystem { // 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 @@ public class RecoverySystem { 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 395250cb66f8..747f5b8ccf0c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -303,6 +303,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 136d335cf473..8cac6e051e6b 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_PACKAGE_ACCESS; 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) { + private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) { wtfIfInLock(); if (wipeExtRequested) { @@ -5312,7 +5313,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // 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 a33153e07496..54717157d069 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 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @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 6393b0b4018e..c58b733b8b54 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 @@ package com.android.server.devicepolicy; 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { // 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 ed8de0517631..8121bcf16c60 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 class MockSystemServices { } 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 { } } |