Merge "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."
diff --git a/Android.mk b/Android.mk
index 35ee3fd..95f38c5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -120,6 +120,7 @@
 	core/java/android/os/IMessenger.aidl \
 	core/java/android/os/storage/IMountService.aidl \
 	core/java/android/os/storage/IMountServiceListener.aidl \
+	core/java/android/os/storage/IMountShutdownObserver.aidl \
 	core/java/android/os/INetworkManagementService.aidl \
 	core/java/android/os/INetStatService.aidl \
 	core/java/android/os/IPermissionController.aidl \
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index b3df522..5a79215 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -18,7 +18,6 @@
 
 import java.io.IOException;
 import android.os.ServiceManager;
-import android.os.storage.IMountService;
 
 /**
  * Class that provides access to some of the power management functions.
@@ -101,15 +100,6 @@
      */
     public static void reboot(String reason) throws IOException
     {
-        IMountService mSvc = IMountService.Stub.asInterface(
-                ServiceManager.getService("mount"));
-
-        if (mSvc != null) {
-            try {
-                mSvc.shutdown();
-            } catch (Exception e) {
-            }
-        }
         rebootNative(reason);
     }
 
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
index ad4cb10..75455ab 100644
--- a/core/java/android/os/storage/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -18,6 +18,7 @@
 package android.os.storage;
 
 import android.os.storage.IMountServiceListener;
+import android.os.storage.IMountShutdownObserver;
 
 /** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
  * In particular, the ordering of the methods below must match the 
@@ -142,6 +143,7 @@
 
     /**
      * Shuts down the MountService and gracefully unmounts all external media.
+     * Invokes call back once the shutdown is complete.
      */
-    void shutdown();
+    void shutdown(IMountShutdownObserver observer);
 }
diff --git a/core/java/android/os/storage/IMountShutdownObserver.aidl b/core/java/android/os/storage/IMountShutdownObserver.aidl
new file mode 100644
index 0000000..0aa8a45
--- /dev/null
+++ b/core/java/android/os/storage/IMountShutdownObserver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Callback class for receiving events related
+ * to shutdown.
+ *
+ * @hide - For internal consumption only.
+ */
+interface IMountShutdownObserver {
+    /**
+     * This method is called when the shutdown
+     * of MountService completed.
+     * @param statusCode indicates success or failure
+     * of the shutdown.
+     */
+    void onShutDownComplete(int statusCode);
+}
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index 37898a1..51cd0f8 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -28,12 +28,13 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Handler;
+import android.os.Power;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.Power;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.storage.IMountService;
+import android.os.storage.IMountShutdownObserver;
 
 import com.android.internal.telephony.ITelephony;
 import android.util.Log;
@@ -46,16 +47,20 @@
     private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
     // maximum time we wait for the shutdown broadcast before going on.
     private static final int MAX_BROADCAST_TIME = 10*1000;
+    private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
     
     // state tracking
     private static Object sIsStartedGuard = new Object();
     private static boolean sIsStarted = false;
     
+    private static boolean mReboot;
+    private static String mRebootReason;
+
     // static instance of this thread
     private static final ShutdownThread sInstance = new ShutdownThread();
     
-    private final Object mBroadcastDoneSync = new Object();
-    private boolean mBroadcastDone;
+    private final Object mActionDoneSync = new Object();
+    private boolean mActionDone;
     private Context mContext;
     private PowerManager mPowerManager;
     private PowerManager.WakeLock mWakeLock;
@@ -64,12 +69,13 @@
     private ShutdownThread() {
     }
  
-    /** 
+    /**
      * Request a clean shutdown, waiting for subsystems to clean up their
      * state etc.  Must be called from a Looper thread in which its UI
      * is shown.
-     * 
+     *
      * @param context Context used to display the shutdown progress dialog.
+     * @param confirm true if user confirmation is needed before shutting down.
      */
     public static void shutdown(final Context context, boolean confirm) {
         // ensure that only one thread is trying to power down.
@@ -106,6 +112,21 @@
         }
     }
 
+    /**
+     * Request a clean shutdown, waiting for subsystems to clean up their
+     * state etc.  Must be called from a Looper thread in which its UI
+     * is shown.
+     *
+     * @param context Context used to display the shutdown progress dialog.
+     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
+     * @param confirm true if user confirmation is needed before shutting down.
+     */
+    public static void reboot(final Context context, String reason, boolean confirm) {
+        mReboot = true;
+        mRebootReason = reason;
+        shutdown(context, confirm);
+    }
+
     private static void beginShutdownSequence(Context context) {
         synchronized (sIsStartedGuard) {
             sIsStarted = true;
@@ -145,13 +166,13 @@
         sInstance.start();
     }
 
-    void broadcastDone() {
-        synchronized (mBroadcastDoneSync) {
-            mBroadcastDone = true;
-            mBroadcastDoneSync.notifyAll();
+    void actionDone() {
+        synchronized (mActionDoneSync) {
+            mActionDone = true;
+            mActionDoneSync.notifyAll();
         }
     }
-    
+
     /**
      * Makes sure we handle the shutdown gracefully.
      * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
@@ -163,27 +184,27 @@
         BroadcastReceiver br = new BroadcastReceiver() {
             @Override public void onReceive(Context context, Intent intent) {
                 // We don't allow apps to cancel this, so ignore the result.
-                broadcastDone();
+                actionDone();
             }
         };
         
         Log.i(TAG, "Sending shutdown broadcast...");
         
         // First send the high-level shut down broadcast.
-        mBroadcastDone = false;
+        mActionDone = false;
         mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
                 br, mHandler, 0, null, null);
         
         final long endTime = System.currentTimeMillis() + MAX_BROADCAST_TIME;
-        synchronized (mBroadcastDoneSync) {
-            while (!mBroadcastDone) {
+        synchronized (mActionDoneSync) {
+            while (!mActionDone) {
                 long delay = endTime - System.currentTimeMillis();
                 if (delay <= 0) {
                     Log.w(TAG, "Shutdown broadcast timed out");
                     break;
                 }
                 try {
-                    mBroadcastDoneSync.wait(delay);
+                    mActionDoneSync.wait(delay);
                 } catch (InterruptedException e) {
                 }
             }
@@ -262,17 +283,50 @@
         }
 
         // Shutdown MountService to ensure media is in a safe state
-        try {
-            if (mount != null) {
-                mount.shutdown();
-            } else {
-                Log.w(TAG, "MountService unavailable for shutdown");
+        IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
+            public void onShutDownComplete(int statusCode) throws RemoteException {
+                Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
+                actionDone();
             }
-        } catch (Exception e) {
-            Log.e(TAG, "Exception during MountService shutdown", e);
+        };
+
+        Log.i(TAG, "Shutting down MountService");
+        // Set initial variables and time out time.
+        mActionDone = false;
+        final long endShutTime = System.currentTimeMillis() + MAX_SHUTDOWN_WAIT_TIME;
+        synchronized (mActionDoneSync) {
+            try {
+                if (mount != null) {
+                    mount.shutdown(observer);
+                } else {
+                    Log.w(TAG, "MountService unavailable for shutdown");
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Exception during MountService shutdown", e);
+            }
+            while (!mActionDone) {
+                long delay = endShutTime - System.currentTimeMillis();
+                if (delay <= 0) {
+                    Log.w(TAG, "Shutdown wait timed out");
+                    break;
+                }
+                try {
+                    mActionDoneSync.wait(delay);
+                } catch (InterruptedException e) {
+                }
+            }
         }
 
-        //shutdown power
+        if (mReboot) {
+            Log.i(TAG, "Rebooting, reason: " + mRebootReason);
+            try {
+                Power.reboot(mRebootReason);
+            } catch (Exception e) {
+                Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
+            }
+        }
+
+        // Shutdown power
         Log.i(TAG, "Performing low-level shutdown...");
         Power.shutdown();
     }
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));
         }
     }
 
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 0189c60..fc6bfcd 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.app.ShutdownThread;
 import com.android.server.am.BatteryStatsService;
 
 import android.app.ActivityManagerNative;
@@ -41,7 +42,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.storage.IMountService;
 import android.os.IPowerManager;
 import android.os.LocalPowerManager;
 import android.os.Power;
@@ -2202,28 +2202,35 @@
     {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
 
-        /*
-         * Manually shutdown the MountService to ensure media is
-         * put into a safe state.
-         */
-        IMountService mSvc = IMountService.Stub.asInterface(
-                ServiceManager.getService("mount"));
-
-        if (mSvc != null) {
-            try {
-                mSvc.shutdown();
-            } catch (Exception e) {
-                Slog.e(TAG, "MountService shutdown failed", e);
+        if (mHandler == null || !ActivityManagerNative.isSystemReady()) {
+            throw new IllegalStateException("Too early to call reboot()");
+        }
+        
+        final String finalReason = reason;
+        Runnable runnable = new Runnable() {
+            public void run() {
+                synchronized (this) {
+                    ShutdownThread.reboot(mContext, finalReason, false);
+                    // if we get here we failed
+                    notify();
+                }
+                
             }
-        } else {
-            Slog.w(TAG, "MountService unavailable for shutdown");
-        }
+        };
 
-        try {
-            Power.reboot(reason);
-        } catch (IOException e) {
-            Slog.e(TAG, "reboot failed", e);
+        mHandler.post(runnable);
+
+        // block until we reboot or fail.
+        // throw an exception if we failed to reboot
+        synchronized (runnable) {
+            try {
+                runnable.wait();
+            } catch (InterruptedException e) {
+            }
         }
+     
+        // if we get here we failed
+        throw new IllegalStateException("unable to reboot!");
     }
 
     /**
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index f97f50a..3f64b25 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -29,6 +29,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -676,11 +677,8 @@
      */
     void rebootSystem(String reason) {
         Slog.i(TAG, "Rebooting system because: " + reason);
-        try {
-            android.os.Power.reboot(reason);
-        } catch (IOException e) {
-            Slog.e(TAG, "Reboot failed!", e);
-        }
+        PowerManagerService pms = (PowerManagerService) ServiceManager.getService("power");
+        pms.reboot(reason);
     }
 
     /**
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index e06c3a8..1f0bf4d 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -35,6 +35,7 @@
     <uses-permission android:name="android.permission.ASEC_DESTROY" />
     <uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT" />
     <uses-permission android:name="android.permission.ASEC_RENAME" />
+    <uses-permission android:name="android.permission.SHUTDOWN" />
     <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
diff --git a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
index c7bacd4..9aed363 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java
@@ -16,6 +16,8 @@
 
 package com.android.unit_tests;
 
+import com.android.unit_tests.PackageManagerTests.StorageListener;
+
 import android.os.storage.IMountService.Stub;
 
 import android.net.Uri;
@@ -41,6 +43,9 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.storage.IMountService;
+import android.os.storage.IMountShutdownObserver;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageResultCode;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -365,4 +370,256 @@
         }
     }
 
+    /*------------ Tests for unmounting volume ---*/
+    public final long MAX_WAIT_TIME=120*1000;
+    public final long WAIT_TIME_INCR=20*1000;
+    boolean getMediaState() {
+        try {
+        String mPath = Environment.getExternalStorageDirectory().toString();
+        String state = getMs().getVolumeState(mPath);
+        return Environment.MEDIA_MOUNTED.equals(state);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    boolean mountMedia() {
+        if (getMediaState()) {
+            return true;
+        }
+        try {
+        String mPath = Environment.getExternalStorageDirectory().toString();
+        int ret = getMs().mountVolume(mPath);
+        return ret == StorageResultCode.OperationSucceeded;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    class StorageListener extends StorageEventListener {
+        String oldState;
+        String newState;
+        String path;
+        private boolean doneFlag = false;
+
+        public void action() {
+            synchronized (this) {
+                doneFlag = true;
+                notifyAll();
+            }
+        }
+
+        public boolean isDone() {
+            return doneFlag;
+        }
+
+        @Override
+        public void onStorageStateChanged(String path, String oldState, String newState) {
+            if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
+            this.oldState = oldState;
+            this.newState = newState;
+            this.path = path;
+            action();
+        }
+    }
+
+    private boolean unmountMedia() {
+        if (!getMediaState()) {
+            return true;
+        }
+        String path = Environment.getExternalStorageDirectory().toString();
+        StorageListener observer = new StorageListener();
+        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+        sm.registerListener(observer);
+        try {
+            // Wait on observer
+            synchronized(observer) {
+                getMs().unmountVolume(path, false);
+                long waitTime = 0;
+                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+                    observer.wait(WAIT_TIME_INCR);
+                    waitTime += WAIT_TIME_INCR;
+                }
+                if(!observer.isDone()) {
+                    throw new Exception("Timed out waiting for packageInstalled callback");
+                }
+                return true;
+            }
+        } catch (Exception e) {
+            return false;
+        } finally {
+            sm.unregisterListener(observer);
+        }
+    }
+    public void testUnmount() {
+        boolean oldStatus = getMediaState();
+        Log.i(TAG, "oldStatus="+oldStatus);
+        try {
+            // Mount media firsts
+            if (!getMediaState()) {
+                mountMedia();
+            }
+            assertTrue(unmountMedia());
+        } finally {
+            // Restore old status
+            boolean currStatus = getMediaState();
+            if (oldStatus != currStatus) {
+                if (oldStatus) {
+                    // Mount media
+                    mountMedia();
+                } else {
+                    unmountMedia();
+                }
+            }
+        }
+    }
+
+    class MultipleStorageLis extends StorageListener {
+        int count = 0;
+        public void onStorageStateChanged(String path, String oldState, String newState) {
+            count++;
+            super.action();
+        }
+    }
+    /*
+     * This test invokes unmount multiple time and expects the call back
+     * to be invoked just once.
+     */
+    public void testUnmountMultiple() {
+        boolean oldStatus = getMediaState();
+        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+        MultipleStorageLis observer = new MultipleStorageLis();
+        try {
+            // Mount media firsts
+            if (!getMediaState()) {
+                mountMedia();
+            }
+            String path = Environment.getExternalStorageDirectory().toString();
+            sm.registerListener(observer);
+            // Wait on observer
+            synchronized(observer) {
+                for (int i = 0; i < 5; i++) {
+                    getMs().unmountVolume(path, false);
+                }
+                long waitTime = 0;
+                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+                    observer.wait(WAIT_TIME_INCR);
+                    waitTime += WAIT_TIME_INCR;
+                }
+                if(!observer.isDone()) {
+                    failStr("Timed out waiting for packageInstalled callback");
+                }
+            }
+            assertEquals(observer.count, 1);
+        } catch (Exception e) {
+            failStr(e);
+        } finally {
+            sm.unregisterListener(observer);
+            // Restore old status
+            boolean currStatus = getMediaState();
+            if (oldStatus != currStatus) {
+                if (oldStatus) {
+                    // Mount media
+                    mountMedia();
+                } else {
+                    unmountMedia();
+                }
+            }
+        }
+    }
+    
+    class ShutdownObserver extends  IMountShutdownObserver.Stub{
+        private boolean doneFlag = false;
+        int statusCode;
+
+        public void action() {
+            synchronized (this) {
+                doneFlag = true;
+                notifyAll();
+            }
+        }
+
+        public boolean isDone() {
+            return doneFlag;
+        }
+        public void onShutDownComplete(int statusCode) throws RemoteException {
+            this.statusCode = statusCode;
+            action();
+        }
+        
+    }
+
+    boolean invokeShutdown() {
+        IMountService ms = getMs();
+        ShutdownObserver observer = new ShutdownObserver();
+        synchronized (observer) {
+            try {
+                ms.shutdown(observer);
+                return true;
+            } catch (RemoteException e) {
+                failStr(e);
+            }
+        }
+        return false;
+    }
+
+    public void testShutdown() {
+        boolean oldStatus = getMediaState();
+        try {
+            // Mount media firsts
+            if (!getMediaState()) {
+                mountMedia();
+            }
+            assertTrue(invokeShutdown());
+        } finally {
+            // Restore old status
+            boolean currStatus = getMediaState();
+            if (oldStatus != currStatus) {
+                if (oldStatus) {
+                    // Mount media
+                    mountMedia();
+                } else {
+                    unmountMedia();
+                }
+            }
+        }
+    }
+
+    /*
+     * This test invokes unmount multiple time and expects the call back
+     * to be invoked just once.
+     */
+    public void testShutdownMultiple() {
+        boolean oldStatus = getMediaState();
+        try {
+            // Mount media firsts
+            if (!getMediaState()) {
+                mountMedia();
+            }
+            IMountService ms = getMs();
+            ShutdownObserver observer = new ShutdownObserver();
+            synchronized (observer) {
+                try {
+                    ms.shutdown(observer);
+                    for (int i = 0; i < 4; i++) {
+                        ms.shutdown(null);
+                    }
+                } catch (RemoteException e) {
+                    failStr(e);
+                }
+            }
+        } finally {
+            // Restore old status
+            boolean currStatus = getMediaState();
+            if (oldStatus != currStatus) {
+                if (oldStatus) {
+                    // Mount media
+                    mountMedia();
+                } else {
+                    unmountMedia();
+                }
+            }
+        }
+    }
+
 }