Add new shutdown observer for MountService.
Use new observer before rebooting and shutting down.
Add some unit tests for unmount and shutdown code paths
Fix registering/unregistering part in MountService
Use ShutdownThread in PowerManager.reboot()
Add reboot support to ShutdownThread.
Remove MountService code from PowerManagerService.java and Power.java.
Clean shutdown/reboot is handled exclusively by ShutdownThread now.

Change-Id: Iefb157451d3d9c426cb431707b870a873c09123d
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 0974f7f..713358d 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -26,6 +26,7 @@
 import android.net.Uri;
 import android.os.storage.IMountService;
 import android.os.storage.IMountServiceListener;
+import android.os.storage.IMountShutdownObserver;
 import android.os.storage.StorageResultCode;
 import android.os.Handler;
 import android.os.Message;
@@ -172,69 +173,110 @@
         }
     }
 
+    class ShutdownCallBack extends UnmountCallBack {
+        IMountShutdownObserver observer;
+        ShutdownCallBack(String path, IMountShutdownObserver observer) {
+            super(path, true);
+            this.observer = observer;
+        }
+
+        @Override
+        void handleFinished() {
+            int ret = doUnmountVolume(path, true);
+            if (observer != null) {
+                try {
+                    observer.onShutDownComplete(ret);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException when shutting down");
+                }
+            }
+        }
+    }
+
     final private Handler mHandler = new Handler() {
         ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
+        boolean mRegistered = false;
+
+        void registerReceiver() {
+            mRegistered = true;
+            mContext.registerReceiver(mPmReceiver, mPmFilter);
+        }
+
+        void unregisterReceiver() {
+            mRegistered = false;
+            mContext.unregisterReceiver(mPmReceiver);
+        }
 
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case H_UNMOUNT_PM_UPDATE: {
                     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
                     mForceUnmounts.add(ucb);
-                    mContext.registerReceiver(mPmReceiver, mPmFilter);
-                    boolean hasExtPkgs = mPms.updateExternalMediaStatus(false);
-                    if (!hasExtPkgs) {
-                        // Unregister right away
-                        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+                    // Register only if needed.
+                    if (!mRegistered) {
+                        registerReceiver();
+                        boolean hasExtPkgs = mPms.updateExternalMediaStatus(false);
+                        if (!hasExtPkgs) {
+                            // Unregister right away
+                            mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+                        }
                     }
                     break;
                 }
                 case H_UNMOUNT_PM_DONE: {
-                    // Unregister receiver
-                    mContext.unregisterReceiver(mPmReceiver);
-                    UnmountCallBack ucb = mForceUnmounts.get(0);
-                    if (ucb == null || ucb.path == null) {
-                        // Just ignore
-                        return;
+                    // Unregister now.
+                    if (mRegistered) {
+                        unregisterReceiver();
                     }
-                    String path = ucb.path;
-                    boolean done = false;
-                    if (!ucb.force) {
-                        done = true;
-                    } else {
-                        int pids[] = getStorageUsers(path);
-                        if (pids == null || pids.length == 0) {
+                    int size = mForceUnmounts.size();
+                    int sizeArr[] = new int[size];
+                    int sizeArrN = 0;
+                    for (int i = 0; i < size; i++) {
+                        UnmountCallBack ucb = mForceUnmounts.get(i);
+                        String path = ucb.path;
+                        boolean done = false;
+                        if (!ucb.force) {
                             done = true;
                         } else {
-                            // Kill processes holding references first
-                            ActivityManagerService ams = (ActivityManagerService)
-                            ServiceManager.getService("activity");
-                            // Eliminate system process here?
-                            boolean ret = ams.killPidsForMemory(pids);
-                            if (ret) {
-                                // Confirm if file references have been freed.
-                                pids = getStorageUsers(path);
-                                if (pids == null || pids.length == 0) {
-                                    done = true;
+                            int pids[] = getStorageUsers(path);
+                            if (pids == null || pids.length == 0) {
+                                done = true;
+                            } else {
+                                // Kill processes holding references first
+                                ActivityManagerService ams = (ActivityManagerService)
+                                ServiceManager.getService("activity");
+                                // Eliminate system process here?
+                                boolean ret = ams.killPidsForMemory(pids);
+                                if (ret) {
+                                    // Confirm if file references have been freed.
+                                    pids = getStorageUsers(path);
+                                    if (pids == null || pids.length == 0) {
+                                        done = true;
+                                    }
                                 }
                             }
                         }
-                    }
-                    if (done) {
-                        mForceUnmounts.remove(0);
-                        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
-                                ucb));
-                    } else {
-                        if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
-                            Log.i(TAG, "Cannot unmount inspite of " +
-                                    MAX_UNMOUNT_RETRIES + " to unmount media");
-                            // Send final broadcast indicating failure to unmount.                      
+                        if (done) {
+                            sizeArr[sizeArrN++] = i;
+                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
+                                    ucb));
                         } else {
-                            mHandler.sendMessageDelayed(
-                                    mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
-                                            ucb.retries++),
-                                    RETRY_UNMOUNT_DELAY);
+                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
+                                Log.i(TAG, "Cannot unmount inspite of " +
+                                        MAX_UNMOUNT_RETRIES + " to unmount media");
+                                // Send final broadcast indicating failure to unmount.                 
+                            } else {
+                                mHandler.sendMessageDelayed(
+                                        mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
+                                                ucb.retries++),
+                                        RETRY_UNMOUNT_DELAY);
+                            }
                         }
                     }
+                    // Remove already processed elements from list.
+                    for (int i = (sizeArrN-1); i >= 0; i--) {
+                        mForceUnmounts.remove(sizeArr[i]);
+                    }
                     break;
                 }
                 case H_UNMOUNT_MS : {
@@ -826,7 +868,7 @@
         }
     }
 
-    public void shutdown() {
+    public void shutdown(final IMountShutdownObserver observer) {
         validatePermission(android.Manifest.permission.SHUTDOWN);
 
         Log.i(TAG, "Shutting down");
@@ -865,12 +907,9 @@
         }
 
         if (state.equals(Environment.MEDIA_MOUNTED)) {
-            /*
-             * If the media is mounted, then gracefully unmount it.
-             */
-            if (doUnmountVolume(path, true) != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, "Failed to unmount media for shutdown");
-            }
+            // Post a unmount message.
+            ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
+            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
         }
     }