services: MountService: Refactor MountService for vold2

Squash of the following:
services: MountService: Rework the way volume states are handled
MountService: Add new API for directly getting volume state via a mount point
Environment: Switch from using system property for external storage state.
MountService: Add support for UMS
MountService: Fix a few bugs
services: MountService: Add support for mount-on-insertion
services: MountService: Add some debugging around UMS
services: MountService: Fix some UMS bugs and clean-up startup mount code

Signed-off-by: San Mehat <san@google.com>
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 6212b17..eed2af7 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,8 @@
 
 import java.io.File;
 
+import android.os.IMountService;
+
 /**
  * Provides access to environment variables.
  */
@@ -28,6 +30,8 @@
 
     private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
 
+    private static IMountService mMntSvc = null;
+
     /**
      * Gets the Android root directory.
      */
@@ -167,9 +171,19 @@
 
     /**
      * Gets the current state of the external storage device.
+     * Note: This call should be deprecated as it doesn't support
+     * multiple volumes.
      */
     public static String getExternalStorageState() {
-        return SystemProperties.get("EXTERNAL_STORAGE_STATE", MEDIA_REMOVED);
+        try {
+            if (mMntSvc == null) {
+                mMntSvc = IMountService.Stub.asInterface(ServiceManager
+                                                         .getService("mount"));
+            }
+            return mMntSvc.getVolumeState(getExternalStorageDirectory().toString());
+        } catch (android.os.RemoteException rex) {
+            return Environment.MEDIA_REMOVED;
+        }
     }
 
     static File getDirectory(String variableName, String defaultPath) {
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index 96d44b6..447e764 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -75,4 +75,9 @@
      * when a UMS host is detected.
      */
     void setAutoStartUms(boolean value);
+
+    /**
+     * Gets the state of an volume via it's mountpoint.
+     */
+    String getVolumeState(String mountPoint);
 }
diff --git a/services/java/com/android/server/MountListener.java b/services/java/com/android/server/MountListener.java
index 3e53585..d4943ff 100644
--- a/services/java/com/android/server/MountListener.java
+++ b/services/java/com/android/server/MountListener.java
@@ -21,6 +21,7 @@
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.RemoteException;
 import android.util.Config;
 import android.util.Log;
 
@@ -29,163 +30,59 @@
 import java.io.OutputStream;
 import java.net.Socket;
 
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
 /**
- * Thread for communicating with the vol service daemon via a local socket.
- * Events received from the daemon are passed to the MountService instance, 
- * and the MountService instance calls MountListener to send commands to the daemon.
+ * Vold Connection class
  */
 final class MountListener implements Runnable {
-
     private static final String TAG = "MountListener";
-
-    // ** THE FOLLOWING STRING CONSTANTS MUST MATCH VALUES IN system/vold/
-    
-    // socket name for connecting to vold
     private static final String VOLD_SOCKET = "vold";
-    
-    // vold commands
-    private static final String VOLD_CMD_ENABLE_UMS = "enable_ums";
-    private static final String VOLD_CMD_DISABLE_UMS = "disable_ums";
-    private static final String VOLD_CMD_SEND_UMS_STATUS = "send_ums_status";
-    private static final String VOLD_CMD_MOUNT_VOLUME = "mount_volume:";
-    private static final String VOLD_CMD_EJECT_MEDIA = "eject_media:";
-    private static final String VOLD_CMD_FORMAT_MEDIA = "format_media:";
+    private static final int    RESPONSE_QUEUE_SIZE = 10;
 
-    // vold events
-    private static final String VOLD_EVT_UMS_ENABLED = "ums_enabled";
-    private static final String VOLD_EVT_UMS_DISABLED = "ums_disabled";
-    private static final String VOLD_EVT_UMS_CONNECTED = "ums_connected";
-    private static final String VOLD_EVT_UMS_DISCONNECTED = "ums_disconnected";
+    private MountService          mService;
+    private BlockingQueue<String> mResponseQueue;
+    private OutputStream          mOutputStream;
 
-    private static final String VOLD_EVT_NOMEDIA = "volume_nomedia:";
-    private static final String VOLD_EVT_UNMOUNTED = "volume_unmounted:";
-    private static final String VOLD_EVT_MOUNTED = "volume_mounted:";
-    private static final String VOLD_EVT_MOUNTED_RO = "volume_mounted_ro:";
-    private static final String VOLD_EVT_UMS = "volume_ums";
-    private static final String VOLD_EVT_BAD_REMOVAL = "volume_badremoval:";
-    private static final String VOLD_EVT_DAMAGED = "volume_damaged:";
-    private static final String VOLD_EVT_CHECKING = "volume_checking:";
-    private static final String VOLD_EVT_NOFS = "volume_nofs:";
-    private static final String VOLD_EVT_EJECTING = "volume_ejecting:";
+    class ResponseCode {
+        public static final int ShareAvailabilityResult        = 210;
 
-    /**
-     * MountService that handles events received from the vol service daemon
-     */
-    private MountService mService;
-    
-    /**
-     * Stream for sending commands to the vol service daemon.
-     */
-    private OutputStream mOutputStream;
-    
-    /** 
-     * Cached value indicating whether or not USB mass storage is enabled.
-     */
-    private boolean mUmsEnabled;
- 
-    /** 
-     * Cached value indicating whether or not USB mass storage is connected.
-     */
-    private boolean mUmsConnected;
-
-   /**
-     * Constructor for MountListener
-     * 
-     * @param service  The MountListener we are handling communication with USB
-     *                 daemon for.
-     */
-    MountListener(MountService service) { 
-        mService = service;   
+        public static final int UnsolicitedInformational       = 600;
+        public static final int VolumeStateChange              = 605;
+        public static final int VolumeMountFailedBlank         = 610;
+        public static final int VolumeMountFailedDamaged       = 611;
+        public static final int VolumeMountFailedNoMedia       = 612;
+        public static final int ShareAvailabilityChange        = 620;
+        public static final int VolumeDiskInserted             = 630;
+        public static final int VolumeDiskRemoved              = 631;
+        public static final int VolumeBadRemoval               = 632;
     }
 
-    /**
-     * Process and dispatches events received from the vol service daemon
-     * 
-     * @param event  An event received from the vol service daemon
-     */
-    private void handleEvent(String event) {
-        if (Config.LOGD) Log.d(TAG, "handleEvent " + event);
-    
-        int colonIndex = event.indexOf(':');
-        String path = (colonIndex > 0 ? event.substring(colonIndex + 1) : null);
-        
-        if (event.equals(VOLD_EVT_UMS_ENABLED)) {
-            mUmsEnabled = true;
-        } else if (event.equals(VOLD_EVT_UMS_DISABLED)) {
-            mUmsEnabled = false;
-        } else if (event.equals(VOLD_EVT_UMS_CONNECTED)) {
-            mUmsConnected = true;
-            mService.notifyUmsConnected();
-        } else if (event.equals(VOLD_EVT_UMS_DISCONNECTED)) {
-            mUmsConnected = false;        
-            mService.notifyUmsDisconnected();
-        } else if (event.startsWith(VOLD_EVT_NOMEDIA)) {
-            mService.notifyMediaRemoved(path);
-        } else if (event.startsWith(VOLD_EVT_UNMOUNTED)) {
-            mService.notifyMediaUnmounted(path);
-        } else if (event.startsWith(VOLD_EVT_CHECKING)) {
-            mService.notifyMediaChecking(path);
-        } else if (event.startsWith(VOLD_EVT_NOFS)) {
-            mService.notifyMediaNoFs(path);
-        } else if (event.startsWith(VOLD_EVT_MOUNTED)) {
-            mService.notifyMediaMounted(path, false);
-        } else if (event.startsWith(VOLD_EVT_MOUNTED_RO)) {
-            mService.notifyMediaMounted(path, true);
-        } else if (event.startsWith(VOLD_EVT_UMS)) {
-            mService.notifyMediaShared(path);
-        } else if (event.startsWith(VOLD_EVT_BAD_REMOVAL)) {
-            mService.notifyMediaBadRemoval(path);
-            // also send media eject intent, to notify apps to close any open
-            // files on the media.
-            mService.notifyMediaEject(path);
-        } else if (event.startsWith(VOLD_EVT_DAMAGED)) {
-            mService.notifyMediaUnmountable(path);
-        } else if (event.startsWith(VOLD_EVT_EJECTING)) {
-            mService.notifyMediaEject(path);
-        }    
+    MountListener(MountService service) {
+        mService = service;
+        mResponseQueue = new LinkedBlockingQueue<String>(RESPONSE_QUEUE_SIZE);
     }
-    
-    /**
-     * Sends a command to the mount service daemon via a local socket
-     * 
-     * @param command  The command to send to the mount service daemon
-     */
-    private void writeCommand(String command) {
-        writeCommand2(command, null);
-    }
-    
-    /**
-     * Sends a command to the mount service daemon via a local socket
-     * with a single argument
-     * 
-     * @param command  The command to send to the mount service daemon
-     * @param argument The argument to send with the command (or null)
-     */
-    private void writeCommand2(String command, String argument) {
-        synchronized (this) {
-            if (mOutputStream == null) {
-                Log.e(TAG, "No connection to vold", new IllegalStateException());
-            } else {
-                StringBuilder builder = new StringBuilder(command);
-                if (argument != null) {
-                    builder.append(argument);
-                }
-                builder.append('\0');
 
-                try {
-                    mOutputStream.write(builder.toString().getBytes());
-                } catch (IOException ex) {
-                    Log.e(TAG, "IOException in writeCommand", ex);
-                }
+    public void run() {
+        // Vold does not run in the simulator, so fake out a mounted event to trigger the Media Scanner
+        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+            mService.notifyMediaMounted(Environment.getExternalStorageDirectory().getPath(), false);
+            return;
+        }
+
+        try {
+            while (true) {
+                listenToSocket();
             }
+        } catch (Throwable t) {
+            Log.e(TAG, "Fatal error " + t + " in MountListener thread!");
         }
     }
 
-    /** 
-     * Opens a socket to communicate with the mount service daemon and listens 
-     * for events from the daemon.  
-     *
-     */
     private void listenToSocket() {
        LocalSocket socket = null;
 
@@ -195,15 +92,13 @@
                     LocalSocketAddress.Namespace.RESERVED);
 
             socket.connect(address);
+            mService.onVoldConnected();
 
             InputStream inputStream = socket.getInputStream();
             mOutputStream = socket.getOutputStream();
 
-            byte[] buffer = new byte[100];
+            byte[] buffer = new byte[4096];
 
-            writeCommand(VOLD_CMD_SEND_UMS_STATUS);
-            mountMedia(Environment.getExternalStorageDirectory().getAbsolutePath());
-            
             while (true) {
                 int count = inputStream.read(buffer);
                 if (count < 0) break;
@@ -212,16 +107,36 @@
                 for (int i = 0; i < count; i++) {
                     if (buffer[i] == 0) {
                         String event = new String(buffer, start, i - start);
-                        handleEvent(event);
+//                        Log.d(TAG, "Got packet {" + event + "}");
+
+                        String[] tokens = event.split(" ");
+                        try {
+                            int code = Integer.parseInt(tokens[0]);
+
+                            if (code >= ResponseCode.UnsolicitedInformational) {
+                                try {
+                                    handleUnsolicitedEvent(code, event, tokens);
+                                } catch (Exception ex) {
+                                    Log.e(TAG, "Error handling unsolicited event '" + event + "'");
+                                    Log.e(TAG, ex.toString());
+                                }
+                            } else {
+                                try {
+                                    mResponseQueue.put(event);
+                                } catch (InterruptedException ex) {
+                                    Log.e(TAG, "InterruptedException");
+                                }
+                            }
+                        } catch (NumberFormatException nfe) {
+                            Log.w(TAG,
+                                  "Unknown msg from Vold '" + event + "'");
+                        }
                         start = i + 1;
                     }                   
                 }
             }                
         } catch (IOException ex) {
-            // This exception is normal when running in desktop simulator 
-            // where there is no mount daemon to talk to
-
-            // log("IOException in listenToSocket");
+            Log.e(TAG, "IOException in listenToSocket");
         }
         
         synchronized (this) {
@@ -244,46 +159,123 @@
             Log.w(TAG, "IOException closing socket");
         }
        
-        /*
-         * Sleep before trying again.
-         * This should not happen except while debugging.
-         * Without this sleep, the emulator will spin and
-         * create tons of throwaway LocalSockets, making
-         * system_server GC constantly.
-         */
-        Log.e(TAG, "Failed to connect to vold", new IllegalStateException());
-        SystemClock.sleep(2000);
+        Log.e(TAG, "Failed to connect to Vold", new IllegalStateException());
+        SystemClock.sleep(5000);
+    }
+
+    private void handleUnsolicitedEvent(int code, String raw, String[] cooked) throws RemoteException {
+//        Log.d(TAG, "unsolicited {" + raw + "}");
+        if (code == ResponseCode.VolumeStateChange) {
+            // FMT: NNN Volume <label> <mountpoint> state changed from <old_#> (<old_str>) to <new_#> (<new_str>)
+            mService.notifyVolumeStateChange(cooked[2], cooked[3],
+                                             Integer.parseInt(cooked[7]),
+                                             Integer.parseInt(cooked[10]));
+        } else if (code == ResponseCode.VolumeMountFailedBlank) {
+            // FMT: NNN Volume <label> <mountpoint> mount failed - no supported file-systems
+            mService.notifyMediaNoFs(cooked[3]);
+            // FMT: NNN Volume <label> <mountpoint> mount failed - no media
+        } else if (code == ResponseCode.VolumeMountFailedNoMedia) {
+            mService.notifyMediaRemoved(cooked[3]);
+        } else if (code == ResponseCode.VolumeMountFailedDamaged) {
+            // FMT: NNN Volume <label> <mountpoint> mount failed - filesystem check failed
+            mService.notifyMediaUnmountable(cooked[3]);
+        } else if (code == ResponseCode.ShareAvailabilityChange) {
+            // FMT: NNN Share method <method> now <available|unavailable>
+            boolean avail = false;
+            if (cooked[5].equals("available")) {
+                avail = true;
+            }
+            mService.notifyShareAvailabilityChange(cooked[3], avail);
+        } else if (code == ResponseCode.VolumeDiskInserted) {
+            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
+            mService.notifyMediaInserted(cooked[3]);
+        } else if (code == ResponseCode.VolumeDiskRemoved) {
+            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
+            mService.notifyMediaRemoved(cooked[3]);
+        } else if (code == ResponseCode.VolumeBadRemoval) {
+            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
+            mService.notifyMediaBadRemoval(cooked[3]);
+        } else {
+            Log.d(TAG, "Unhandled event {" + raw + "}");
+        }
+    }
+    
+
+    private void sendCommand(String command) {
+        sendCommand(command, null);
     }
 
     /**
-     * Main loop for MountListener thread.
+     * Sends a command to Vold with a single argument
+     *
+     * @param command  The command to send to the mount service daemon
+     * @param argument The argument to send with the command (or null)
      */
-    public void run() {
-        // ugly hack for the simulator.
-        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
-            SystemProperties.set("EXTERNAL_STORAGE_STATE", Environment.MEDIA_MOUNTED);
-            // usbd does not run in the simulator, so send a fake device mounted event to trigger the Media Scanner
-            mService.notifyMediaMounted(Environment.getExternalStorageDirectory().getPath(), false);
-            
-            // no usbd in the simulator, so no point in hanging around.
-            return;
-        }
-    
-        try {  
-            while (true) {
-                listenToSocket();
+    private void sendCommand(String command, String argument) {
+        synchronized (this) {
+            Log.d(TAG, "sendCommand {" + command + "} {" + argument + "}");
+            if (mOutputStream == null) {
+                Log.e(TAG, "No connection to Vold", new IllegalStateException());
+            } else {
+                StringBuilder builder = new StringBuilder(command);
+                if (argument != null) {
+                    builder.append(argument);
+                }
+                builder.append('\0');
+
+                try {
+                    mOutputStream.write(builder.toString().getBytes());
+                } catch (IOException ex) {
+                    Log.e(TAG, "IOException in sendCommand", ex);
+                }
             }
-        } catch (Throwable t) {
-            // catch all Throwables so we don't bring down the system process
-            Log.e(TAG, "Fatal error " + t + " in MountListener thread!");
         }
     }
-    
-    /**
-     * @return  true if USB mass storage is enabled
-     */
-    boolean getMassStorageEnabled() {
-        return mUmsEnabled;
+
+    private synchronized ArrayList<String> doCommand(String cmd) throws RemoteException {
+        sendCommand(cmd);
+
+        ArrayList<String> response = new ArrayList<String>();
+        boolean complete = false;
+        int code = -1;
+
+        while (!complete) {
+            try {
+                String line = mResponseQueue.take();
+//                Log.d(TAG, "Removed off queue -> " + line);
+                String[] tokens = line.split(" ");
+                code = Integer.parseInt(tokens[0]);
+
+                if ((code >= 200) && (code < 600))
+                    complete = true;
+                response.add(line);
+            } catch (InterruptedException ex) {
+                Log.e(TAG, "InterruptedException");
+            }
+        }
+
+        if (code >= 400 && code < 600) {
+            Log.w(TAG, "Vold cmd {" + cmd + "} err code " + code);
+            throw new RemoteException();
+        }
+        return response;
+    }
+
+    boolean getShareAvailable(String method) throws RemoteException {
+        ArrayList<String> rsp = doCommand("share_available " + method);
+
+        for (String line : rsp) {
+            String []tok = line.split(" ");
+            int code = Integer.parseInt(tok[0]);
+            if (code == ResponseCode.ShareAvailabilityResult) {
+                if (tok[2].equals("available"))
+                    return true;
+                return false;
+            } else {
+                throw new RemoteException();
+            }
+        }
+        throw new RemoteException();
     }
 
     /**
@@ -291,35 +283,28 @@
      * 
      * @param enable  true to enable USB mass storage support
      */
-    void setMassStorageEnabled(boolean enable) {
-        writeCommand(enable ? VOLD_CMD_ENABLE_UMS : VOLD_CMD_DISABLE_UMS);
-    }
-
-    /**
-     * @return  true if USB mass storage is connected
-     */
-    boolean getMassStorageConnected() {
-        return mUmsConnected;
+    void setShareMethodEnabled(String mountPoint, String method, boolean enable) throws RemoteException {
+        doCommand((enable ? "" : "un") + "share " + mountPoint + " " + method);
     }
 
     /**
      * Mount media at given mount point.
      */
-    public void mountMedia(String mountPoint) {
-        writeCommand2(VOLD_CMD_MOUNT_VOLUME, mountPoint);
+    public void mountVolume(String label) throws RemoteException {
+        doCommand("mount " + label);
     }
 
     /**
      * Unmount media at given mount point.
      */
-    public void ejectMedia(String mountPoint) {
-        writeCommand2(VOLD_CMD_EJECT_MEDIA, mountPoint);
+    public void unmountVolume(String label) throws RemoteException {
+        doCommand("unmount " + label);
     }
 
     /**
      * Format media at given mount point.
      */
-    public void formatMedia(String mountPoint) {
-        writeCommand2(VOLD_CMD_FORMAT_MEDIA, mountPoint);
+    public void formatVolume(String label) throws RemoteException {
+        doCommand("format " + label);
     }
 }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 204389e..93617d1 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -45,6 +45,19 @@
     
     private static final String TAG = "MountService";
 
+    class VolumeState {
+        public static final int Init       = -1;
+        public static final int NoMedia    = 0;
+        public static final int Idle       = 1;
+        public static final int Pending    = 2;
+        public static final int Checking   = 3;
+        public static final int Mounted    = 4;
+        public static final int Unmounting = 5;
+        public static final int Formatting = 6;
+        public static final int Shared     = 7;
+        public static final int SharedMnt  = 8;
+    }
+
     /**
      * Binder context for this service
      */
@@ -84,6 +97,11 @@
 
     private boolean mAutoStartUms;
 
+    private boolean mUmsConnected = false;
+    private boolean mUmsEnabled = false;
+
+    private String  mLegacyState = Environment.MEDIA_REMOVED;
+
     /**
      * Constructs a new MountService instance
      * 
@@ -119,7 +137,7 @@
      * @return true if USB mass storage support is enabled.
      */
     public boolean getMassStorageEnabled() throws RemoteException {
-        return mListener.getMassStorageEnabled();
+        return mUmsEnabled;
     }
 
     /**
@@ -128,15 +146,53 @@
      * @param enable  true to enable USB mass storage support
      */
     public void setMassStorageEnabled(boolean enable) throws RemoteException {
-        mListener.setMassStorageEnabled(enable);
+        try {
+            String vp = Environment.getExternalStorageDirectory().getPath();
+            String vs = getVolumeState(vp);
+
+            if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
+                Log.d(TAG, "Unmounting media before UMS enable");
+                unmountMedia(vp);
+            }
+
+            mListener.setShareMethodEnabled(Environment
+                                            .getExternalStorageDirectory()
+                                            .getPath(),
+                                            "ums", enable);
+            mUmsEnabled = enable;
+            if (!enable) {
+                Log.d(TAG, "Mounting media after UMS disable");
+                mountMedia(vp);
+            }
+        } catch (RemoteException rex) {
+            Log.e(TAG, "Failed to set ums enable {" + enable + "}");
+            return;
+        }
     }
 
     /**
      * @return true if USB mass storage is connected.
      */
     public boolean getMassStorageConnected() throws RemoteException {
-        return mListener.getMassStorageConnected();
+        return mUmsConnected;
     }
+
+    /**
+     * @return state of the volume at the specified mount point
+     */
+    public String getVolumeState(String mountPoint) throws RemoteException {
+        /*
+         * XXX: Until we have multiple volume discovery, just hardwire
+         * this to /sdcard
+         */
+        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
+            Log.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
+            throw new IllegalArgumentException();
+        }
+
+        return mLegacyState;
+    }
+
     
     /**
      * Attempt to mount external media
@@ -147,7 +203,7 @@
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
         }
-        mListener.mountMedia(mountPath);
+        mListener.mountVolume(mountPath);
     }
 
     /**
@@ -165,7 +221,7 @@
         mShowSafeUnmountNotificationWhenUnmounted = true;
 
         // tell mountd to unmount the media
-        mListener.ejectMedia(mountPath);
+        mListener.unmountVolume(mountPath);
     }
 
     /**
@@ -178,7 +234,7 @@
             throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
         }
 
-        mListener.formatMedia(formatPath);
+        mListener.formatVolume(formatPath);
     }
 
     /**
@@ -221,6 +277,15 @@
         SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0"));
     }
 
+    void updatePublicVolumeState(String mountPoint, String state) {
+        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
+            Log.w(TAG, "Multiple volumes not currently supported");
+            return;
+        }
+        Log.w(TAG, "State for {" + mountPoint + "} = {" + state + "}");
+        mLegacyState = state;
+    }
+
     /**
      * Update the state of the USB mass storage notification
      */
@@ -255,10 +320,73 @@
         }
     }
 
+    void onVoldConnected() {
+        new Thread() {
+            public void run() {
+                try {
+                    if (!getVolumeState(Environment.getExternalStorageDirectory().getPath())
+                                 .equals(Environment.MEDIA_MOUNTED)) {
+                        try {
+                            mountMedia(Environment.getExternalStorageDirectory().getPath());
+                            Log.d(TAG, "Connection-mount suceeded");
+                        } catch (Exception ex) {
+                            Log.w(TAG, "Connection-mount failed");
+                        }
+                    } else {
+                        Log.d(TAG, "Skipping connection-mount; already mounted");
+                    }
+                } catch (RemoteException rex) {
+                    Log.e(TAG, "Exception while handling connection mount " + rex);
+                }
+
+                try {
+                    boolean avail = mListener.getShareAvailable("ums");
+                    notifyShareAvailabilityChange("ums", avail);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Failed to get share availability");
+                }
+            }
+        }.start();
+    }
+
+    void notifyVolumeStateChange(String label, String mountPoint, int oldState,
+                                 int newState) throws RemoteException {
+        String vs = getVolumeState(mountPoint);
+
+        if (newState == VolumeState.Init) {
+        } else if (newState == VolumeState.NoMedia) {
+            // NoMedia is handled via Disk Remove events
+        } else if (newState == VolumeState.Idle) {
+            // Don't notify if we're in BAD_REMOVAL, NOFS, or UNMOUNTABLE
+            if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) &&
+                !vs.equals(Environment.MEDIA_NOFS) &&
+                !vs.equals(Environment.MEDIA_UNMOUNTABLE)) {
+                notifyMediaUnmounted(mountPoint);
+            }
+        } else if (newState == VolumeState.Pending) {
+        } else if (newState == VolumeState.Checking) {
+            notifyMediaChecking(mountPoint);
+        } else if (newState == VolumeState.Mounted) {
+            notifyMediaMounted(mountPoint, false);
+        } else if (newState == VolumeState.Unmounting) {
+            notifyMediaUnmounting(mountPoint);
+        } else if (newState == VolumeState.Formatting) {
+        } else if (newState == VolumeState.Shared) {
+            notifyMediaShared(mountPoint, false);
+        } else if (newState == VolumeState.SharedMnt) {
+            notifyMediaShared(mountPoint, true);
+        } else {
+            Log.e(TAG, "Unhandled VolumeState {" + newState + "}");
+        }
+    }
+
+
     /**
      * Broadcasts the USB mass storage connected event to all clients.
      */
     void notifyUmsConnected() {
+        mUmsConnected = true;
+
         String storageState = Environment.getExternalStorageState();
         if (!storageState.equals(Environment.MEDIA_REMOVED) &&
             !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
@@ -278,28 +406,64 @@
         mContext.sendBroadcast(intent);
     }
 
+    void notifyShareAvailabilityChange(String method, boolean avail) {
+        Log.d(TAG, "Share method {" + method + "} availability now " + avail);
+        if (!method.equals("ums")) {
+           Log.w(TAG, "Ignoring unsupported share method {" + method + "}");
+           return;
+        }
+        if (avail) {
+            notifyUmsConnected();
+        } else {
+            notifyUmsDisconnected();
+        }
+    }
+
     /**
      * Broadcasts the USB mass storage disconnected event to all clients.
      */
     void notifyUmsDisconnected() {
+        mUmsConnected = false;
         updateUsbMassStorageNotification(false, false);
         Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
         mContext.sendBroadcast(intent);
     }
 
+    void notifyMediaInserted(final String path) throws RemoteException {
+        new Thread() {
+            public void run() {
+                try {
+                    Log.d(TAG, "Mounting media after insertion");
+                    mountMedia(path);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Failed to mount media on insertion");
+                }
+            }
+        }.start();
+    }
+
     /**
      * Broadcasts the media removed event to all clients.
      */
-    void notifyMediaRemoved(String path) {
+    void notifyMediaRemoved(String path) throws RemoteException {
+
+        // Suppress this on bad removal
+        if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
+            return;
+        }
+
+        updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
+
         updateUsbMassStorageNotification(true, false);
 
         setMediaStorageNotification(
-                com.android.internal.R.string.ext_media_nomedia_notification_title,
-                com.android.internal.R.string.ext_media_nomedia_notification_message,
-                com.android.internal.R.drawable.stat_notify_sdcard_usb,
-                true, false, null);
+            com.android.internal.R.string.ext_media_nomedia_notification_title,
+            com.android.internal.R.string.ext_media_nomedia_notification_message,
+            com.android.internal.R.drawable.stat_notify_sdcard_usb,
+            true, false, null);
         handlePossibleExplicitUnmountBroadcast(path);
 
+        // Log.d(TAG, "Sending ACTION_MEDIA_REMOVED");
         Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -309,6 +473,9 @@
      * Broadcasts the media unmounted event to all clients.
      */
     void notifyMediaUnmounted(String path) {
+
+        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
+
         if (mShowSafeUnmountNotificationWhenUnmounted) {
             setMediaStorageNotification(
                     com.android.internal.R.string.ext_media_safe_unmount_notification_title,
@@ -321,6 +488,7 @@
         }
         updateUsbMassStorageNotification(false, false);
 
+        // Log.d(TAG, "Sending ACTION_MEDIA_UNMOUNTED");
         Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -330,6 +498,8 @@
      * Broadcasts the media checking event to all clients.
      */
     void notifyMediaChecking(String path) {
+        updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
+
         setMediaStorageNotification(
                 com.android.internal.R.string.ext_media_checking_notification_title,
                 com.android.internal.R.string.ext_media_checking_notification_message,
@@ -337,6 +507,7 @@
                 true, false, null);
 
         updateUsbMassStorageNotification(true, false);
+        // Log.d(TAG, "Sending ACTION_MEDIA_CHECKING");
         Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -346,6 +517,7 @@
      * Broadcasts the media nofs event to all clients.
      */
     void notifyMediaNoFs(String path) {
+        updatePublicVolumeState(path, Environment.MEDIA_NOFS);
         
         Intent intent = new Intent();
         intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
@@ -356,6 +528,7 @@
                                     com.android.internal.R.drawable.stat_notify_sdcard_usb,
                                     true, false, pi);
         updateUsbMassStorageNotification(false, false);
+        // Log.d(TAG, "Sending ACTION_MEDIA_NOFS");
         intent = new Intent(Intent.ACTION_MEDIA_NOFS, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -365,8 +538,11 @@
      * Broadcasts the media mounted event to all clients.
      */
     void notifyMediaMounted(String path, boolean readOnly) {
+        updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
+
         setMediaStorageNotification(0, 0, 0, false, false, null);
         updateUsbMassStorageNotification(false, false);
+        // Log.d(TAG, "Sending ACTION_MEDIA_MOUNTED");
         Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 
                 Uri.parse("file://" + path));
         intent.putExtra("read-only", readOnly);
@@ -377,7 +553,14 @@
     /**
      * Broadcasts the media shared event to all clients.
      */
-    void notifyMediaShared(String path) {
+    void notifyMediaShared(String path, boolean mounted) {
+        if (mounted) {
+            Log.e(TAG, "Live shared mounts not supported yet!");
+            return;
+        }
+
+        updatePublicVolumeState(path, Environment.MEDIA_SHARED);
+
         Intent intent = new Intent();
         intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
         PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
@@ -386,6 +569,7 @@
                                   com.android.internal.R.drawable.stat_sys_warning,
                                   false, true, pi);
         handlePossibleExplicitUnmountBroadcast(path);
+        // Log.d(TAG, "Sending ACTION_MEDIA_SHARED");
         intent = new Intent(Intent.ACTION_MEDIA_SHARED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -395,6 +579,8 @@
      * Broadcasts the media bad removal event to all clients.
      */
     void notifyMediaBadRemoval(String path) {
+        updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
+
         updateUsbMassStorageNotification(true, false);
         setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
                                     com.android.internal.R.string.ext_media_badremoval_notification_message,
@@ -402,19 +588,18 @@
                                     true, true, null);
 
         handlePossibleExplicitUnmountBroadcast(path);
+        // Log.d(TAG, "Sending ACTION_MEDIA_BAD_REMOVAL");
         Intent intent = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
-
-        intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 
-                Uri.parse("file://" + path));
-        mContext.sendBroadcast(intent);
     }
 
     /**
      * Broadcasts the media unmountable event to all clients.
      */
     void notifyMediaUnmountable(String path) {
+        updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
+
         Intent intent = new Intent();
         intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
         PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
@@ -427,6 +612,7 @@
 
         handlePossibleExplicitUnmountBroadcast(path);
 
+        // Log.d(TAG, "Sending ACTION_MEDIA_UNMOUNTABLE");
         intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -435,7 +621,8 @@
     /**
      * Broadcasts the media eject event to all clients.
      */
-    void notifyMediaEject(String path) {
+    void notifyMediaUnmounting(String path) {
+        // Log.d(TAG, "Sending ACTION_MEDIA_EJECT");
         Intent intent = new Intent(Intent.ACTION_MEDIA_EJECT, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);