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/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);