Implement issue #3094621 and #3094609 - wipe sd card

3094621: add "wipe sd card" option to factory data reset
3094609: collapse unmount/format into one command

Also since we have decided that it is important to consider
the Crespo storage as internal storage, DevicePolicyManager
gets a new API to be able to wipe it.  (No big deal, since
all of the work for this is now done in the implementation
of the new UI.)

Change-Id: I32a77c410f710a87dcdcbf6586c09bd2e48a8807
diff --git a/api/current.xml b/api/current.xml
index 0efc466..bdc7f2e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -31092,6 +31092,17 @@
  visibility="public"
 >
 </field>
+<field name="WIPE_EXTERNAL_STORAGE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 </package>
 <package name="android.app.backup"
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4736404..5ae8a1f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -763,6 +763,13 @@
         public static final int FLAG_CANT_SAVE_STATE = 1<<0;
         
         /**
+         * Constant for {@link #flags}: this process is associated with a
+         * persistent system app.
+         * @hide
+         */
+        public static final int FLAG_PERSISTENT = 1<<1;
+
+        /**
          * Flags of information.  May be any of
          * {@link #FLAG_CANT_SAVE_STATE}.
          * @hide
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 296d70a4..570351d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -511,6 +511,12 @@
     }
     
     /**
+     * Flag for {@link #wipeData(int)}: also erase the device's external
+     * storage.
+     */
+    public static final int WIPE_EXTERNAL_STORAGE = 0x0001;
+
+    /**
      * Ask the user date be wiped.  This will cause the device to reboot,
      * erasing all user data while next booting up.  External storage such
      * as SD cards will not be erased.
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
new file mode 100644
index 0000000..965022e
--- /dev/null
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -0,0 +1,228 @@
+package com.android.internal.os.storage;
+
+import android.app.ProgressDialog;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ * Takes care of unmounting and formatting external storage.
+ */
+public class ExternalStorageFormatter extends Service
+        implements DialogInterface.OnCancelListener {
+    static final String TAG = "ExternalStorageFormatter";
+
+    public static final String FORMAT_ONLY = "com.android.internal.os.storage.FORMAT_ONLY";
+    public static final String FORMAT_AND_FACTORY_RESET = "com.android.internal.os.storage.FORMAT_AND_FACTORY_RESET";
+
+    public static final String EXTRA_ALWAYS_RESET = "always_reset";
+
+    public static final ComponentName COMPONENT_NAME
+            = new ComponentName("android", ExternalStorageFormatter.class.getName());
+
+    // Access using getMountService()
+    private IMountService mMountService = null;
+
+    private StorageManager mStorageManager = null;
+
+    private PowerManager.WakeLock mWakeLock;
+
+    private ProgressDialog mProgressDialog = null;
+
+    private boolean mFactoryReset = false;
+    private boolean mAlwaysReset = false;
+
+    StorageEventListener mStorageListener = new StorageEventListener() {
+        @Override
+        public void onStorageStateChanged(String path, String oldState, String newState) {
+            Log.i(TAG, "Received storage state changed notification that " +
+                    path + " changed state from " + oldState +
+                    " to " + newState);
+            updateProgressState();
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        if (mStorageManager == null) {
+            mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
+            mStorageManager.registerListener(mStorageListener);
+        }
+
+        mWakeLock = ((PowerManager)getSystemService(Context.POWER_SERVICE))
+                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExternalStorageFormatter");
+        mWakeLock.acquire();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (FORMAT_AND_FACTORY_RESET.equals(intent.getAction())) {
+            mFactoryReset = true;
+        }
+        if (intent.getBooleanExtra(EXTRA_ALWAYS_RESET, false)) {
+            mAlwaysReset = true;
+        }
+
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setIndeterminate(true);
+            mProgressDialog.setCancelable(true);
+            mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            if (!mAlwaysReset) {
+                mProgressDialog.setOnCancelListener(this);
+            }
+            updateProgressState();
+            mProgressDialog.show();
+        }
+
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mStorageManager != null) {
+            mStorageManager.unregisterListener(mStorageListener);
+        }
+        if (mProgressDialog != null) {
+            mProgressDialog.dismiss();
+        }
+        mWakeLock.release();
+        super.onDestroy();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        IMountService mountService = getMountService();
+        String extStoragePath = Environment.getExternalStorageDirectory().toString();
+        try {
+            mountService.mountVolume(extStoragePath);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed talking with mount service", e);
+        }
+        stopSelf();
+    }
+
+    void fail(int msg) {
+        Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
+        if (mAlwaysReset) {
+            sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+        }
+        stopSelf();
+    }
+
+    void updateProgressState() {
+        String status = Environment.getExternalStorageState();
+        if (Environment.MEDIA_MOUNTED.equals(status)
+                || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)) {
+            updateProgressDialog(R.string.progress_unmounting);
+            IMountService mountService = getMountService();
+            String extStoragePath = Environment.getExternalStorageDirectory().toString();
+            try {
+                mountService.unmountVolume(extStoragePath, true);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with mount service", e);
+            }
+        } else if (Environment.MEDIA_NOFS.equals(status)
+                || Environment.MEDIA_UNMOUNTED.equals(status)
+                || Environment.MEDIA_UNMOUNTABLE.equals(status)) {
+            updateProgressDialog(R.string.progress_erasing);
+            final IMountService mountService = getMountService();
+            final String extStoragePath = Environment.getExternalStorageDirectory().toString();
+            if (mountService != null) {
+                new Thread() {
+                    public void run() {
+                        boolean success = false;
+                        try {
+                            mountService.formatVolume(extStoragePath);
+                            success = true;
+                        } catch (Exception e) {
+                            Toast.makeText(ExternalStorageFormatter.this,
+                                    R.string.format_error, Toast.LENGTH_LONG).show();
+                        }
+                        if (success) {
+                            if (mFactoryReset) {
+                                sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+                                // Intent handling is asynchronous -- assume it will happen soon.
+                                stopSelf();
+                                return;
+                            }
+                        }
+                        // 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"));
+                        } else {
+                            try {
+                                mountService.mountVolume(extStoragePath);
+                            } catch (RemoteException e) {
+                                Log.w(TAG, "Failed talking with mount service", e);
+                            }
+                        }
+                        stopSelf();
+                        return;
+                    }
+                }.start();
+            } else {
+                Log.w("MediaFormat", "Unable to locate IMountService");
+            }
+        } else if (Environment.MEDIA_BAD_REMOVAL.equals(status)) {
+            fail(R.string.media_bad_removal);
+        } else if (Environment.MEDIA_CHECKING.equals(status)) {
+            fail(R.string.media_checking);
+        } else if (Environment.MEDIA_REMOVED.equals(status)) {
+            fail(R.string.media_removed);
+        } else if (Environment.MEDIA_SHARED.equals(status)) {
+            fail(R.string.media_shared);
+        } else {
+            fail(R.string.media_unknown_state);
+            Log.w(TAG, "Unknown storage state: " + status);
+            stopSelf();
+        }
+    }
+
+    public void updateProgressDialog(int msg) {
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setIndeterminate(true);
+            mProgressDialog.setCancelable(false);
+            mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            mProgressDialog.show();
+        }
+
+        mProgressDialog.setMessage(getText(msg));
+    }
+
+    IMountService getMountService() {
+        if (mMountService == null) {
+            IBinder service = ServiceManager.getService("mount");
+            if (service != null) {
+                mMountService = IMountService.Stub.asInterface(service);
+            } else {
+                Log.e(TAG, "Can't get mount service");
+            }
+        }
+        return mMountService;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff079e4..ef4ee11 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1371,7 +1371,8 @@
         </receiver>
 
         <receiver android:name="com.android.server.MasterClearReceiver"
-            android:permission="android.permission.MASTER_CLEAR" >
+            android:permission="android.permission.MASTER_CLEAR"
+            android:priority="100" >
             <intent-filter>
                 <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
                 <action android:name="android.intent.action.MASTER_CLEAR" />
@@ -1381,8 +1382,11 @@
                 <category android:name="android.intent.category.MASTER_CLEAR" />
             </intent-filter>
         </receiver>
+
+        <service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
+            android:permission="android.permission.MASTER_CLEAR"
+            android:exported="true" />
+
     </application>
 
 </manifest>
-
-
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 037a362..e1b5d93 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2337,4 +2337,33 @@
     <!-- Shown when the users bandwidth is reduced because of excessive data use -->
     <string name="throttled_notification_title">Mobile data limit exceeded</string>
     <string name="throttled_notification_message">Touch to learn more about mobile data use</string>
+
+    <!-- Strings for ExternalStorageFormatter service. -->
+    <!-- Text for progress dialog while unmounting USB storage volume [CHAR LIMIT=NONE] -->
+    <string name="progress_unmounting" product="nosdcard">Unmounting USB storage...</string>
+    <!-- Text for progress dialog while unmounting SD card [CHAR LIMIT=NONE] -->
+    <string name="progress_unmounting" product="default">Unmounting SD card...</string>
+    <!-- Text for progress dialog while erasing USB storage volume [CHAR LIMIT=NONE] -->
+    <string name="progress_erasing" product="nosdcard">Erasing USB storage...</string>
+    <!-- Text for progress dialog while erasing SD card [CHAR LIMIT=NONE] -->
+    <string name="progress_erasing" product="default">Erasing SD card...</string>
+    <!-- Text for message to user that an error happened when formatting USB storage [CHAR LIMIT=NONE] -->
+    <string name="format_error" product="nosdcard">Failed to erase USB storage.</string>
+    <!-- Text for message to user that an error happened when formatting SD card [CHAR LIMIT=NONE] -->
+    <string name="format_error" product="default">Failed to erase SD card.</string>
+    <!-- Text for message to user that SD card has been removed while in use [CHAR LIMIT=NONE] -->
+    <string name="media_bad_removal">SD card was removed before being unmounted.</string>
+    <!-- Text for message to user USB storage is currently being checked [CHAR LIMIT=NONE] -->
+    <string name="media_checking" product="nosdcard">USB storage is currently being checked.</string>
+    <!-- Text for message to user SD card is currently being checked [CHAR LIMIT=NONE] -->
+    <string name="media_checking" product="default">SD card is currently being checked.</string>
+    <!-- Text for message to user SD card has been removed [CHAR LIMIT=NONE] -->
+    <string name="media_removed">SD card has been removed.</string>
+    <!-- Text for message to user USB storage is currently mounted on a computer [CHAR LIMIT=NONE] -->
+    <string name="media_shared" product="nosdcard">USB storage is currently in use by a computer.</string>
+    <!-- Text for message to user SD card is currently mounted on a computer [CHAR LIMIT=NONE] -->
+    <string name="media_shared" product="default">SD card is currently in use by a computer.</string>
+    <!-- Text for message for an unknown external media state [CHAR LIMIT=NONE] -->
+    <string name="media_unknown_state">External media in unknown state.</string>
+
 </resources>
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 21273cc..1538003 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.storage.ExternalStorageFormatter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
@@ -41,6 +42,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IPowerManager;
+import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
@@ -71,6 +73,7 @@
     
     final Context mContext;
     final MyPackageMonitor mMonitor;
+    final PowerManager.WakeLock mWakeLock;
 
     IPowerManager mIPowerManager;
     
@@ -216,6 +219,8 @@
         mContext = context;
         mMonitor = new MyPackageMonitor();
         mMonitor.register(context, true);
+        mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE))
+                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM");
     }
 
     private IPowerManager getIPowerManager() {
@@ -862,10 +867,17 @@
     }
     
     void wipeDataLocked(int flags) {
-        try {
-            RecoverySystem.rebootWipeUserData(mContext);
-        } catch (IOException e) {
-            Slog.w(TAG, "Failed requesting data wipe", e);
+        if ((flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0) {
+            Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
+            intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
+            mWakeLock.acquire(10000);
+            mContext.startService(intent);
+        } else {
+            try {
+                RecoverySystem.rebootWipeUserData(mContext);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed requesting data wipe", e);
+            }
         }
     }
     
diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java
index 4d04cee..bdb5a24 100644
--- a/services/java/com/android/server/MasterClearReceiver.java
+++ b/services/java/com/android/server/MasterClearReceiver.java
@@ -29,7 +29,7 @@
     private static final String TAG = "MasterClear";
 
     @Override
-    public void onReceive(Context context, Intent intent) {
+    public void onReceive(final Context context, final Intent intent) {
         if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
             if (!"google.com".equals(intent.getStringExtra("from"))) {
                 Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
@@ -37,16 +37,23 @@
             }
         }
 
-        try {
-            Slog.w(TAG, "!!! FACTORY RESET !!!");
-            if (intent.hasExtra("enableEFS")) {
-                RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false));
-            } else {
-                RecoverySystem.rebootWipeUserData(context);
+        Slog.w(TAG, "!!! FACTORY RESET !!!");
+        // The reboot call is blocking, so we need to do it on another thread.
+        Thread thr = new Thread("Reboot") {
+            @Override
+            public void run() {
+                try {
+                    if (intent.hasExtra("enableEFS")) {
+                        RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false));
+                    } else {
+                        RecoverySystem.rebootWipeUserData(context);
+                    }
+                    Log.wtf(TAG, "Still running after master clear?!");
+                } catch (IOException e) {
+                    Slog.e(TAG, "Can't perform master clear/factory reset", e);
+                }
             }
-            Log.wtf(TAG, "Still running after master clear?!");
-        } catch (IOException e) {
-            Slog.e(TAG, "Can't perform master clear/factory reset", e);
-        }
+        };
+        thr.start();
     }
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index de228d9..aa2f784 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -7036,6 +7036,9 @@
                     if (mHeavyWeightProcess == app) {
                         currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE;
                     }
+                    if (app.persistent) {
+                        currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT;
+                    }
                     int adj = app.curAdj;
                     if (adj >= EMPTY_APP_ADJ) {
                         currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY;