am b8bbeff0: am 45ef18b6: Merge "Include reason when wiping data." into lmp-dev

* commit 'b8bbeff0796847bbe98deb2d84989b656356873c':
  Include reason when wiping data.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 702ac6b..77981f4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2723,6 +2723,9 @@
     public static final String
             ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
 
+    /** {@hide} */
+    public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
@@ -3425,6 +3428,9 @@
     public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT =
             "android.intent.extra.TIME_PREF_24_HOUR_FORMAT";
 
+    /** {@hide} */
+    public static final String EXTRA_REASON = "android.intent.extra.REASON";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 7a46e40..b879c83 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.ByteArrayInputStream;
@@ -333,9 +334,10 @@
         throws IOException {
         String filename = packageFile.getCanonicalPath();
         Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
-        String arg = "--update_package=" + filename +
-            "\n--locale=" + Locale.getDefault().toString();
-        bootCommand(context, arg);
+
+        final String filenameArg = "--update_package=" + filename;
+        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        bootCommand(context, filenameArg, localeArg);
     }
 
     /**
@@ -352,7 +354,18 @@
      * @throws SecurityException if the current user is not allowed to wipe data.
      */
     public static void rebootWipeUserData(Context context) throws IOException {
-        rebootWipeUserData(context, false);
+        rebootWipeUserData(context, false, context.getPackageName());
+    }
+
+    /** {@hide} */
+    public static void rebootWipeUserData(Context context, String reason) throws IOException {
+        rebootWipeUserData(context, false, reason);
+    }
+
+    /** {@hide} */
+    public static void rebootWipeUserData(Context context, boolean shutdown)
+            throws IOException {
+        rebootWipeUserData(context, shutdown, context.getPackageName());
     }
 
     /**
@@ -373,8 +386,8 @@
      *
      * @hide
      */
-    public static void rebootWipeUserData(Context context, boolean shutdown)
-        throws IOException {
+    public static void rebootWipeUserData(Context context, boolean shutdown, String reason)
+            throws IOException {
         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
         if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
             throw new SecurityException("Wiping data is not allowed for this user.");
@@ -395,13 +408,18 @@
         // Block until the ordered broadcast has completed.
         condition.block();
 
-        String shutdownArg = "";
+        String shutdownArg = null;
         if (shutdown) {
-            shutdownArg = "--shutdown_after\n";
+            shutdownArg = "--shutdown_after";
         }
 
-        bootCommand(context, shutdownArg + "--wipe_data\n--locale=" +
-                    Locale.getDefault().toString());
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
     }
 
     /**
@@ -409,23 +427,38 @@
      * @throws IOException if something goes wrong.
      */
     public static void rebootWipeCache(Context context) throws IOException {
-        bootCommand(context, "--wipe_cache\n--locale=" + Locale.getDefault().toString());
+        rebootWipeCache(context, context.getPackageName());
+    }
+
+    /** {@hide} */
+    public static void rebootWipeCache(Context context, String reason) throws IOException {
+        String reasonArg = null;
+        if (!TextUtils.isEmpty(reason)) {
+            reasonArg = "--reason=" + sanitizeArg(reason);
+        }
+
+        final String localeArg = "--locale=" + Locale.getDefault().toString();
+        bootCommand(context, "--wipe_cache", reasonArg, localeArg);
     }
 
     /**
      * Reboot into the recovery system with the supplied argument.
-     * @param arg to pass to the recovery utility.
+     * @param args to pass to the recovery utility.
      * @throws IOException if something goes wrong.
      */
-    private static void bootCommand(Context context, String arg) throws IOException {
+    private static void bootCommand(Context context, String... args) throws IOException {
         RECOVERY_DIR.mkdirs();  // In case we need it
         COMMAND_FILE.delete();  // In case it's not writable
         LOG_FILE.delete();
 
         FileWriter command = new FileWriter(COMMAND_FILE);
         try {
-            command.write(arg);
-            command.write("\n");
+            for (String arg : args) {
+                if (!TextUtils.isEmpty(arg)) {
+                    command.write(arg);
+                    command.write("\n");
+                }
+            }
         } finally {
             command.close();
         }
@@ -470,5 +503,15 @@
         return log;
     }
 
+    /**
+     * Internally, recovery treats each line of the command file as a separate
+     * argv, so we only need to protect against newlines and nulls.
+     */
+    private static String sanitizeArg(String arg) {
+        arg = arg.replace('\0', '?');
+        arg = arg.replace('\n', '?');
+        return arg;
+    }
+
     private void RecoverySystem() { }  // Do not instantiate
 }
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
index fb7f215..a529923 100644
--- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -50,6 +50,7 @@
 
     private boolean mFactoryReset = false;
     private boolean mAlwaysReset = false;
+    private String mReason = null;
 
     StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
@@ -84,6 +85,7 @@
             mAlwaysReset = true;
         }
 
+        mReason = intent.getStringExtra(Intent.EXTRA_REASON);
         mStorageVolume = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
 
         if (mProgressDialog == null) {
@@ -135,7 +137,10 @@
     void fail(int msg) {
         Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
         if (mAlwaysReset) {
-            sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+            Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            intent.putExtra(Intent.EXTRA_REASON, mReason);
+            sendBroadcast(intent);
         }
         stopSelf();
     }
@@ -179,7 +184,10 @@
                         }
                         if (success) {
                             if (mFactoryReset) {
-                                sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+                                Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+                                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                                intent.putExtra(Intent.EXTRA_REASON, mReason);
+                                sendBroadcast(intent);
                                 // Intent handling is asynchronous -- assume it will happen soon.
                                 stopSelf();
                                 return;
@@ -188,7 +196,10 @@
                         // If we didn't succeed, or aren't doing a full factory
                         // reset, then it is time to remount the storage.
                         if (!success && mAlwaysReset) {
-                            sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+                            Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+                            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                            intent.putExtra(Intent.EXTRA_REASON, mReason);
+                            sendBroadcast(intent);
                         } else {
                             try {
                                 mountService.mountVolume(extStoragePath);
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index e88bdf8..f1d5aa3 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -38,6 +38,7 @@
         }
 
         final boolean shutdown = intent.getBooleanExtra("shutdown", false);
+        final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
 
         Slog.w(TAG, "!!! FACTORY RESET !!!");
         // The reboot call is blocking, so we need to do it on another thread.
@@ -45,7 +46,7 @@
             @Override
             public void run() {
                 try {
-                    RecoverySystem.rebootWipeUserData(context, shutdown);
+                    RecoverySystem.rebootWipeUserData(context, shutdown, reason);
                     Log.wtf(TAG, "Still running after master clear?!");
                 } catch (IOException e) {
                     Slog.e(TAG, "Can't perform master clear/factory reset", e);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 59d3dc8..d1aba3c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2862,7 +2862,7 @@
         return false;
     }
 
-    void wipeDataLocked(int flags) {
+    void wipeDataLocked(int flags, String reason) {
         // If the SD card is encrypted and non-removable, we have to force a wipe.
         boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted();
         boolean wipeExtRequested = (flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0;
@@ -2871,12 +2871,13 @@
         if ((forceExtWipe || wipeExtRequested) && !Environment.isExternalStorageEmulated()) {
             Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
             intent.putExtra(ExternalStorageFormatter.EXTRA_ALWAYS_RESET, true);
+            intent.putExtra(Intent.EXTRA_REASON, reason);
             intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
             mWakeLock.acquire(10000);
             mContext.startService(intent);
         } else {
             try {
-                RecoverySystem.rebootWipeUserData(mContext);
+                RecoverySystem.rebootWipeUserData(mContext, reason);
             } catch (IOException e) {
                 Slog.w(LOG_TAG, "Failed requesting data wipe", e);
             } catch (SecurityException e) {
@@ -2885,6 +2886,7 @@
         }
     }
 
+    @Override
     public void wipeData(int flags, final int userHandle) {
         if (!mHasFeature) {
             return;
@@ -2896,20 +2898,34 @@
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(null,
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
                     DeviceAdminInfo.USES_POLICY_WIPE_DATA);
+
+            final String source;
+            if (admin != null && admin.info != null) {
+                final ComponentName cname = admin.info.getComponent();
+                if (cname != null) {
+                    source = cname.flattenToShortString();
+                } else {
+                    source = admin.info.getPackageName();
+                }
+            } else {
+                source = "?";
+            }
+
             long ident = Binder.clearCallingIdentity();
             try {
-                wipeDeviceOrUserLocked(flags, userHandle);
+                wipeDeviceOrUserLocked(flags, userHandle,
+                        "DevicePolicyManager.wipeData() from " + source);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
     }
 
-    private void wipeDeviceOrUserLocked(int flags, final int userHandle) {
+    private void wipeDeviceOrUserLocked(int flags, final int userHandle, String reason) {
         if (userHandle == UserHandle.USER_OWNER) {
-            wipeDataLocked(flags);
+            wipeDataLocked(flags, reason);
         } else {
             mHandler.post(new Runnable() {
                 public void run() {
@@ -3061,7 +3077,7 @@
             }
             if (wipeData) {
                 // Call without holding lock.
-                wipeDeviceOrUserLocked(0, identifier);
+                wipeDeviceOrUserLocked(0, identifier, "reportFailedPasswordAttempt()");
             }
         } finally {
             Binder.restoreCallingIdentity(ident);