auto import from //branches/cupcake/...@130745
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 002ebed..0feb1da 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -19,15 +19,19 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.IMountService;
 import android.os.Environment;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UEventObserver;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
@@ -52,24 +56,32 @@
     private MountListener mListener;
 
     /**
-     * The notification that is shown when USB is connected. It leads the user
-     * to a dialog to enable mass storage mode.
+     * The notification that is shown when a USB mass storage host
+     * is connected. 
      * <p>
-     * This is lazily created, so use {@link #getUsbStorageNotification()}.
+     * This is lazily created, so use {@link #setUsbStorageNotification()}.
      */
     private Notification mUsbStorageNotification;
 
-    private class SdDoorListener extends UEventObserver {    
-        static final String SD_DOOR_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/sd-door";
-        static final String SD_DOOR_SWITCH_NAME = "sd-door";
 
-        public void onUEvent(UEvent event) {
-            if (SD_DOOR_SWITCH_NAME.equals(event.get("SWITCH_NAME"))) {
-                sdDoorStateChanged(event.get("SWITCH_STATE"));
-            }
-        }
-    };
+    /**
+     * The notification that is shown when the following media events occur:
+     *     - Media is being checked
+     *     - Media is blank (or unknown filesystem)
+     *     - Media is corrupt
+     *     - Media is safe to unmount
+     *     - Media is missing
+     * <p>
+     * This is lazily created, so use {@link #setMediaStorageNotification()}.
+     */
+    private Notification mMediaStorageNotification;
     
+    private boolean mShowSafeUnmountNotificationWhenUnmounted;
+
+    private boolean mPlaySounds;
+
+    private boolean mMounted;
+
     /**
      * Constructs a new MountService instance
      * 
@@ -77,13 +89,28 @@
      */
     public MountService(Context context) {
         mContext = context;
+
+        // Register a BOOT_COMPLETED handler so that we can start
+        // MountListener. We defer the startup so that we don't
+        // start processing events before we ought-to
+        mContext.registerReceiver(mBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+
         mListener =  new MountListener(this);       
-        Thread thread = new Thread(mListener, MountListener.class.getName());
-        thread.start();
-        SdDoorListener sdDoorListener = new SdDoorListener();
-        sdDoorListener.startObserving(SdDoorListener.SD_DOOR_UEVENT_MATCH);
+        mShowSafeUnmountNotificationWhenUnmounted = false;
+
+        mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1");
     }
 
+    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+                Thread thread = new Thread(mListener, MountListener.class.getName());
+                thread.start();
+            }
+        }
+    };
+
     /**
      * @return true if USB mass storage support is enabled.
      */
@@ -129,19 +156,92 @@
             throw new SecurityException("Requires MOUNT_UNMOUNT_FILESYSTEMS permission");
         }
 
+        // Set a flag so that when we get the unmounted event, we know
+        // to display the notification
+        mShowSafeUnmountNotificationWhenUnmounted = true;
+
         // tell mountd to unmount the media
         mListener.ejectMedia(mountPath);
     }
 
     /**
+     * Attempt to format external media
+     */
+    public void formatMedia(String formatPath) throws RemoteException {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) 
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires MOUNT_FORMAT_FILESYSTEMS permission");
+        }
+
+        mListener.formatMedia(formatPath);
+    }
+
+    /**
+     * Returns true if we're playing media notification sounds.
+     */
+    public boolean getPlayNotificationSounds() {
+        return mPlaySounds;
+    }
+
+    /**
+     * Set whether or not we're playing media notification sounds.
+     */
+    public void setPlayNotificationSounds(boolean enabled) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SETTINGS) 
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires WRITE_SETTINGS permission");
+        }
+        mPlaySounds = enabled;
+        SystemProperties.set("persist.service.mount.playsnd", (enabled ? "1" : "0"));
+    }
+
+    /**
+     * Update the state of the USB mass storage notification
+     */
+    void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) {
+
+        try {
+
+            if (getMassStorageConnected() && !suppressIfConnected) {
+                Intent intent = new Intent();
+                intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
+                PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+                setUsbStorageNotification(
+                        com.android.internal.R.string.usb_storage_notification_title,
+                        com.android.internal.R.string.usb_storage_notification_message,
+                        com.android.internal.R.drawable.stat_sys_data_usb,
+                        sound, true, pi);
+            } else {
+                setUsbStorageNotification(0, 0, 0, false, false, null);
+            }
+        } catch (RemoteException e) {
+            // Nothing to do
+        }
+    }
+
+    void handlePossibleExplicitUnmountBroadcast(String path) {
+        if (mMounted) {
+            mMounted = false;
+            Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 
+                    Uri.parse("file://" + path));
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    /**
      * Broadcasts the USB mass storage connected event to all clients.
      */
     void notifyUmsConnected() {
         String storageState = Environment.getExternalStorageState();
         if (!storageState.equals(Environment.MEDIA_REMOVED) &&
-                !storageState.equals(Environment.MEDIA_BAD_REMOVAL)) {
-            setUsbStorageNotificationVisibility(true);
+            !storageState.equals(Environment.MEDIA_BAD_REMOVAL) &&
+            !storageState.equals(Environment.MEDIA_CHECKING)) {
+
+            updateUsbMassStorageNotification(false, true);
         }
+
         Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED);
         mContext.sendBroadcast(intent);
     }
@@ -150,7 +250,7 @@
      * Broadcasts the USB mass storage disconnected event to all clients.
      */
     void notifyUmsDisconnected() {
-        setUsbStorageNotificationVisibility(false);
+        updateUsbMassStorageNotification(false, false);
         Intent intent = new Intent(Intent.ACTION_UMS_DISCONNECTED);
         mContext.sendBroadcast(intent);
     }
@@ -159,6 +259,15 @@
      * Broadcasts the media removed event to all clients.
      */
     void notifyMediaRemoved(String path) {
+        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_sys_no_sim,
+                true, false, null);
+        handlePossibleExplicitUnmountBroadcast(path);
+
         Intent intent = new Intent(Intent.ACTION_MEDIA_REMOVED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
@@ -168,18 +277,68 @@
      * Broadcasts the media unmounted event to all clients.
      */
     void notifyMediaUnmounted(String path) {
+        if (mShowSafeUnmountNotificationWhenUnmounted) {
+            setMediaStorageNotification(
+                    com.android.internal.R.string.ext_media_safe_unmount_notification_title,
+                    com.android.internal.R.string.ext_media_safe_unmount_notification_message,
+                    com.android.internal.R.drawable.stat_notify_sim_toolkit,
+                    true, true, null);
+            mShowSafeUnmountNotificationWhenUnmounted = false;
+        } else {
+            setMediaStorageNotification(0, 0, 0, false, false, null);
+        }
+        updateUsbMassStorageNotification(false, false);
+
         Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
     }
 
     /**
+     * Broadcasts the media checking event to all clients.
+     */
+    void notifyMediaChecking(String path) {
+        setMediaStorageNotification(
+                com.android.internal.R.string.ext_media_checking_notification_title,
+                com.android.internal.R.string.ext_media_checking_notification_message,
+                com.android.internal.R.drawable.stat_notify_sim_toolkit,
+                true, false, null);
+
+        updateUsbMassStorageNotification(true, false);
+        Intent intent = new Intent(Intent.ACTION_MEDIA_CHECKING, 
+                Uri.parse("file://" + path));
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Broadcasts the media nofs event to all clients.
+     */
+    void notifyMediaNoFs(String path) {
+        
+        Intent intent = new Intent();
+        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        setMediaStorageNotification(com.android.internal.R.string.ext_media_nofs_notification_title,
+                                    com.android.internal.R.string.ext_media_nofs_notification_message,
+                                    com.android.internal.R.drawable.stat_sys_no_sim,
+                                    true, false, pi);
+        updateUsbMassStorageNotification(false, false);
+        intent = new Intent(Intent.ACTION_MEDIA_NOFS, 
+                Uri.parse("file://" + path));
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
      * Broadcasts the media mounted event to all clients.
      */
     void notifyMediaMounted(String path, boolean readOnly) {
+        setMediaStorageNotification(0, 0, 0, false, false, null);
+        updateUsbMassStorageNotification(false, false);
         Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 
                 Uri.parse("file://" + path));
         intent.putExtra("read-only", readOnly);
+        mMounted = true;
         mContext.sendBroadcast(intent);
     }
 
@@ -187,7 +346,15 @@
      * Broadcasts the media shared event to all clients.
      */
     void notifyMediaShared(String path) {
-        Intent intent = new Intent(Intent.ACTION_MEDIA_SHARED, 
+        Intent intent = new Intent();
+        intent.setClass(mContext, com.android.internal.app.UsbStorageStopActivity.class);
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+        setUsbStorageNotification(com.android.internal.R.string.usb_storage_stop_notification_title,
+                                  com.android.internal.R.string.usb_storage_stop_notification_message,
+                                  com.android.internal.R.drawable.stat_sys_warning,
+                                  false, true, pi);
+        handlePossibleExplicitUnmountBroadcast(path);
+        intent = new Intent(Intent.ACTION_MEDIA_SHARED, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
     }
@@ -196,16 +363,39 @@
      * Broadcasts the media bad removal event to all clients.
      */
     void notifyMediaBadRemoval(String path) {
+        updateUsbMassStorageNotification(true, false);
+        setMediaStorageNotification(com.android.internal.R.string.ext_media_badremoval_notification_title,
+                                    com.android.internal.R.string.ext_media_badremoval_notification_message,
+                                    com.android.internal.R.drawable.stat_sys_warning,
+                                    true, true, null);
+
+        handlePossibleExplicitUnmountBroadcast(path);
         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) {
-        Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 
+        Intent intent = new Intent();
+        intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class);
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        setMediaStorageNotification(com.android.internal.R.string.ext_media_unmountable_notification_title,
+                                    com.android.internal.R.string.ext_media_unmountable_notification_message,
+                                    com.android.internal.R.drawable.stat_sys_no_sim,
+                                    true, false, pi); 
+        updateUsbMassStorageNotification(false, false);
+
+        handlePossibleExplicitUnmountBroadcast(path);
+
+        intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTABLE, 
                 Uri.parse("file://" + path));
         mContext.sendBroadcast(intent);
     }
@@ -219,90 +409,129 @@
         mContext.sendBroadcast(intent);
     }
     
-    private void sdDoorStateChanged(String doorState) {
-        File directory = Environment.getExternalStorageDirectory();
-        String storageState = Environment.getExternalStorageState();
-        
-        if (directory != null) {
-            try {
-                if (doorState.equals("open") && (storageState.equals(Environment.MEDIA_MOUNTED) ||
-                        storageState.equals(Environment.MEDIA_MOUNTED_READ_ONLY))) {
-                    // request SD card unmount if SD card door is opened
-                    unmountMedia(directory.getPath());
-                } else if (doorState.equals("closed") && storageState.equals(Environment.MEDIA_UNMOUNTED)) {
-                    // attempt to remount SD card
-                    mountMedia(directory.getPath());
-                }
-            } catch (RemoteException e) {
-                // Nothing to do.
-            }
-        }
-    }
-
     /**
-     * Sets the visibility of the USB storage notification. This should be
-     * called when a USB cable is connected and also when it is disconnected.
-     * 
-     * @param visible Whether to show or hide the notification.
+     * Sets the USB storage notification.
      */
-    private void setUsbStorageNotificationVisibility(boolean visible) {
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager == null) {
+    private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, boolean sound, boolean visible,
+                                                        PendingIntent pi) {
+
+        if (!visible && mUsbStorageNotification == null) {
             return;
         }
 
-        /*
-         * The convention for notification IDs is to use the icon's resource ID
-         * when the icon is only used by a single notification type, which is
-         * the case here.
-         */
-        Notification notification = getUsbStorageNotification();
-        final int notificationId = notification.icon;
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (notificationManager == null) {
+            return;
+        }
         
         if (visible) {
-            notificationManager.notify(notificationId, notification);
+            Resources r = Resources.getSystem();
+            CharSequence title = r.getText(titleId);
+            CharSequence message = r.getText(messageId);
+
+            if (mUsbStorageNotification == null) {
+                mUsbStorageNotification = new Notification();
+                mUsbStorageNotification.icon = icon;
+                mUsbStorageNotification.when = 0;
+            }
+
+            if (sound && mPlaySounds) {
+                mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
+            } else {
+                mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND;
+            }
+                
+            mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
+
+            mUsbStorageNotification.tickerText = title;
+            if (pi == null) {
+                Intent intent = new Intent();
+                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+            }
+
+            mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi);
+        }
+    
+        final int notificationId = mUsbStorageNotification.icon;
+        if (visible) {
+            notificationManager.notify(notificationId, mUsbStorageNotification);
         } else {
             notificationManager.cancel(notificationId);
         }
     }
 
-    /**
-     * Gets the USB storage notification.
-     * 
-     * @return A {@link Notification} that leads to the dialog to enable USB storage.
-     */
-    private synchronized Notification getUsbStorageNotification() {
-        Resources r = Resources.getSystem();
-        CharSequence title =
-                r.getText(com.android.internal.R.string.usb_storage_notification_title);
-        CharSequence message =
-                r.getText(com.android.internal.R.string.usb_storage_notification_message);
+    private synchronized boolean getMediaStorageNotificationDismissable() {
+        if ((mMediaStorageNotification != null) &&
+            ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) ==
+                    Notification.FLAG_AUTO_CANCEL))
+            return true;
 
-        if (mUsbStorageNotification == null) {
-            mUsbStorageNotification = new Notification();
-            mUsbStorageNotification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
-            mUsbStorageNotification.when = 0;
-            mUsbStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
-            mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND;
+        return false;
+    }
+
+    /**
+     * Sets the media storage notification.
+     */
+    private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible,
+                                                          boolean dismissable, PendingIntent pi) {
+
+        if (!visible && mMediaStorageNotification == null) {
+            return;
         }
 
-        mUsbStorageNotification.tickerText = title;
-        mUsbStorageNotification.setLatestEventInfo(mContext, title, message,
-                getUsbStorageDialogIntent());
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
 
-        return mUsbStorageNotification;
-    }
+        if (notificationManager == null) {
+            return;
+        }
+
+        if (mMediaStorageNotification != null && visible) {
+            /*
+             * Dismiss the previous notification - we're about to
+             * re-use it.
+             */
+            final int notificationId = mMediaStorageNotification.icon;
+            notificationManager.cancel(notificationId);
+        }
+        
+        if (visible) {
+            Resources r = Resources.getSystem();
+            CharSequence title = r.getText(titleId);
+            CharSequence message = r.getText(messageId);
+
+            if (mMediaStorageNotification == null) {
+                mMediaStorageNotification = new Notification();
+                mMediaStorageNotification.when = 0;
+                if (mPlaySounds) {
+                    mMediaStorageNotification.defaults |= Notification.DEFAULT_SOUND;
+                }
+            }
+
+            if (dismissable) {
+                mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL;
+            } else {
+                mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT;
+            }
+
+            mMediaStorageNotification.tickerText = title;
+            if (pi == null) {
+                Intent intent = new Intent();
+                pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+            }
+
+            mMediaStorageNotification.icon = icon;
+            mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi);
+        }
     
-    /**
-     * Creates a pending intent to start the USB storage activity.
-     * 
-     * @return A {@link PendingIntent} that start the USB storage activity.
-     */
-    private PendingIntent getUsbStorageDialogIntent() {
-        Intent intent = new Intent();
-        intent.setClass(mContext, com.android.internal.app.UsbStorageActivity.class);
-        return PendingIntent.getActivity(mContext, 0, intent, 0);
+        final int notificationId = mMediaStorageNotification.icon;
+        if (visible) {
+            notificationManager.notify(notificationId, mMediaStorageNotification);
+        } else {
+            notificationManager.cancel(notificationId);
+        }
     }
 }