Merge "Add single-package restore to Bmgr feature set"
diff --git a/api/current.xml b/api/current.xml
index 492bb34..394a68a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -120110,7 +120110,7 @@
  visibility="public"
 >
 <method name="disableUsbMassStorage"
- return="int"
+ return="void"
  abstract="false"
  native="false"
  synchronized="false"
@@ -120121,7 +120121,7 @@
 >
 </method>
 <method name="enableUsbMassStorage"
- return="int"
+ return="void"
  abstract="false"
  native="false"
  synchronized="false"
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
index 2b2dcf4..ad4cb10 100644
--- a/core/java/android/os/storage/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -45,8 +45,10 @@
 
     /**
      * Enables / disables USB mass storage.
+     * The caller should check actual status of enabling/disabling
+     * USB mass storage via StorageEventListener.
      */
-    int setUsbMassStorageEnabled(boolean enable);
+    void setUsbMassStorageEnabled(boolean enable);
 
     /**
      * Returns true if a USB mass storage host is enabled (media is shared)
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e421ea5..b49979c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -243,30 +243,24 @@
 
     /**
      * Enables USB Mass Storage (UMS) on the device.
-     * @return an integer value representing the outcome of the operation.
-     * @see android.os.storage.StorageResultCode
      */
-    public int enableUsbMassStorage() {
+    public void enableUsbMassStorage() {
         try {
-            return mMountService.setUsbMassStorageEnabled(true);
+            mMountService.setUsbMassStorageEnabled(true);
         } catch (Exception ex) {
             Log.e(TAG, "Failed to enable UMS", ex);
         }
-        return StorageResultCode.OperationFailedInternalError;
     }
 
     /**
      * Disables USB Mass Storage (UMS) on the device.
-     * @return an integer value representing the outcome of the operation.
-     * @see android.os.storage.StorageResultCode
      */
-    public int disableUsbMassStorage() {
+    public void disableUsbMassStorage() {
         try {
-            return mMountService.setUsbMassStorageEnabled(false);
+            mMountService.setUsbMassStorageEnabled(false);
         } catch (Exception ex) {
             Log.e(TAG, "Failed to disable UMS", ex);
         }
-        return StorageResultCode.OperationFailedInternalError;
     }
 
     /**
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b791bf8..b5c59c5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2067,7 +2067,6 @@
     <!-- See USB_STORAGE. This is the message. -->
     <string name="usb_storage_notification_message">Select to copy files to/from your computer.</string>
 
-
     <!-- USB_STORAGE_STOP: While USB storage is enabled, we show a notification dialog asking if he wants to stop. This is the title -->
     <string name="usb_storage_stop_notification_title">Turn off USB storage</string>
     <!-- See USB_STORAGE. This is the message. -->
@@ -2084,6 +2083,15 @@
     <!-- See USB_STORAGE_STOP_DIALOG.  If there was an error stopping, this is the text. -->
     <string name="usb_storage_stop_error_message">There was a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again.</string>
 
+    <!-- USB_STORAGE_KILL_STORAGE_USERS dialog  -->
+    <string name="dlg_confirm_kill_storage_users_title">Enable Mass Storage</string>
+    <!-- USB_STORAGE_KILL_STORAGE_USERS dialog message text -->
+    <string name="dlg_confirm_kill_storage_users_text">Some processes accessing data on sdcard will be killed. Do you want to continue?</string>
+    <!-- USB_STORAGE_ERROR dialog  dialog-->
+    <string name="dlg_error_title">UMS operation failed</string>
+    <!-- USB_STORAGE_ERROR dialog  ok button-->
+    <string name="dlg_ok">OK</string>
+
     <!-- External media format dialog strings -->
     <!-- This is the label for the activity, and should never be visible to the user. -->
     <!-- See EXTMEDIA_FORMAT.  EXTMEDIA_FORMAT_DIALOG:  After the user selects the notification, a dialog is shown asking if he wants to format the SD card.  This is the title. -->
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 4485c79..41f3850 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -111,15 +111,18 @@
     private String                                mLegacyState = Environment.MEDIA_REMOVED;
     private PackageManagerService                 mPms;
     private boolean                               mUmsEnabling;
-    private ArrayList<MountServiceBinderListener> mListeners;
+    // Used as a lock for methods that register/unregister listeners.
+    final private ArrayList<MountServiceBinderListener> mListeners =
+            new ArrayList<MountServiceBinderListener>();
     private boolean                               mBooted = false;
     private boolean                               mReady = false;
     private boolean                               mSendUmsConnectedOnBoot = false;
 
     /**
      * Private hash of currently mounted secure containers.
+     * Used as a lock in methods to manipulate secure containers.
      */
-    private HashSet<String> mAsecMountSet = new HashSet<String>();
+    final private HashSet<String> mAsecMountSet = new HashSet<String>();
 
     private static final int H_UNMOUNT_PM_UPDATE = 1;
     private static final int H_UNMOUNT_PM_DONE = 2;
@@ -148,6 +151,25 @@
             this.path = path;
             this.force = force;
         }
+
+        void handleFinished() {
+            doUnmountVolume(path, true);
+        }
+    }
+
+    class UmsEnableCallBack extends UnmountCallBack {
+        String method;
+
+        UmsEnableCallBack(String path, String method, boolean force) {
+            super(path, force);
+            this.method = method;
+        }
+
+        @Override
+        void handleFinished() {
+            super.handleFinished();
+            doShareUnshareVolume(path, method, true);
+        }
     }
 
     final private Handler mHandler = new Handler() {
@@ -217,8 +239,7 @@
                 }
                 case H_UNMOUNT_MS : {
                     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
-                    String path = ucb.path;
-                    doUnmountVolume(path, true);
+                    ucb.handleFinished();
                     break;
                 }
             }
@@ -298,52 +319,18 @@
         }
     }
 
-    private int doShareUnshareVolume(String path, String method, boolean enable) {
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-
+    private void doShareUnshareVolume(String path, String method, boolean enable) {
         // TODO: Add support for multiple share methods
         if (!method.equals("ums")) {
             throw new IllegalArgumentException(String.format("Method %s not supported", method));
         }
 
-        /*
-         * If the volume is mounted and we're enabling then unmount it
-         */
-        String vs = getVolumeState(path);
-        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
-            mUmsEnabling = enable; // Override for isUsbMassStorageEnabled()
-            int rc = doUnmountVolume(path, true);
-            mUmsEnabling = false; // Clear override
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, String.format("Failed to unmount before enabling UMS (%d)", rc));
-                return rc;
-            }
-        }
-
         try {
             mConnector.doCommand(String.format(
                     "volume %sshare %s %s", (enable ? "" : "un"), path, method));
         } catch (NativeDaemonConnectorException e) {
             Log.e(TAG, "Failed to share/unshare", e);
-            return StorageResultCode.OperationFailedInternalError;
         }
-
-        /*
-         * If we disabled UMS then mount the volume
-         */
-        if (!enable) {
-            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, String.format(
-                        "Failed to remount %s after disabling share method %s", path, method));
-                /*
-                 * Even though the mount failed, the unshare didn't so don't indicate an error.
-                 * The mountVolume() call will have set the storage state and sent the necessary
-                 * broadcasts.
-                 */
-            }
-        }
-
-        return StorageResultCode.OperationSucceeded;
     }
 
     private void updatePublicVolumeState(String path, String state) {
@@ -547,7 +534,7 @@
             if (!vs.equals(
                     Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
                             Environment.MEDIA_NOFS) && !vs.equals(
-                                    Environment.MEDIA_UNMOUNTABLE) && !mUmsEnabling) {
+                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
                 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
                 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
             }
@@ -791,8 +778,6 @@
         mContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
 
-        mListeners = new ArrayList<MountServiceBinderListener>();
-
         /*
          * Vold does not run in the simulator, so pretend the connector thread
          * ran and did its thing.
@@ -852,9 +837,7 @@
              * the UMS host could have dirty FAT cache entries
              * yet to flush.
              */
-            if (setUsbMassStorageEnabled(false) != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, "UMS disable on shutdown failed");
-            }
+            setUsbMassStorageEnabled(false);
         } else if (state.equals(Environment.MEDIA_CHECKING)) {
             /*
              * If the media is being checked, then we need to wait for
@@ -886,19 +869,62 @@
         }
     }
 
+    private boolean getUmsEnabling() {
+        synchronized (mListeners) {
+            return mUmsEnabling;
+        }
+    }
+
+    private void setUmsEnabling(boolean enable) {
+        synchronized (mListeners) {
+            mUmsEnabling = true;
+        }
+    }
+
     public boolean isUsbMassStorageConnected() {
         waitForReady();
 
-        if (mUmsEnabling) {
+        if (getUmsEnabling()) {
             return true;
         }
         return doGetShareMethodAvailable("ums");
     }
 
-    public int setUsbMassStorageEnabled(boolean enable) {
+    public void setUsbMassStorageEnabled(boolean enable) {
         waitForReady();
+        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
-        return doShareUnshareVolume(Environment.getExternalStorageDirectory().getPath(), "ums", enable);
+        // TODO: Add support for multiple share methods
+
+        /*
+         * If the volume is mounted and we're enabling then unmount it
+         */
+        String path = Environment.getExternalStorageDirectory().getPath();
+        String vs = getVolumeState(path);
+        String method = "ums";
+        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
+            // Override for isUsbMassStorageEnabled()
+            setUmsEnabling(enable);
+            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
+            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
+            // Clear override
+            setUmsEnabling(false);
+        }
+        /*
+         * If we disabled UMS then mount the volume
+         */
+        if (!enable) {
+            doShareUnshareVolume(path, method, enable);
+            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
+                Log.e(TAG, "Failed to remount " + path +
+                        " after disabling share method " + method);
+                /*
+                 * Even though the mount failed, the unshare didn't so don't indicate an error.
+                 * The mountVolume() call will have set the storage state and sent the necessary
+                 * broadcasts.
+                 */
+            }
+        }
     }
 
     public boolean isUsbMassStorageEnabled() {
diff --git a/services/java/com/android/server/status/UsbStorageActivity.java b/services/java/com/android/server/status/UsbStorageActivity.java
index 7a2a2d6..c1c8c22 100644
--- a/services/java/com/android/server/status/UsbStorageActivity.java
+++ b/services/java/com/android/server/status/UsbStorageActivity.java
@@ -16,25 +16,27 @@
 
 package com.android.server.status;
 
+import com.android.internal.R;
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.DialogInterface.OnCancelListener;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Environment;
+import android.os.IBinder;
+import android.os.storage.IMountService;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageEventListener;
-import android.os.storage.StorageResultCode;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.widget.ImageView;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.Toast;
 import android.view.View;
 import android.util.Log;
 
@@ -43,7 +45,8 @@
  * on-demand (that is, when the USB cable is connected). It uses the alert
  * dialog style. It will be launched from a notification.
  */
-public class UsbStorageActivity extends Activity {
+public class UsbStorageActivity extends Activity
+        implements View.OnClickListener, OnCancelListener {
     private static final String TAG = "UsbStorageActivity";
     private Button mMountButton;
     private Button mUnmountButton;
@@ -51,6 +54,9 @@
     private TextView mMessage;
     private ImageView mIcon;
     private StorageManager mStorageManager = null;
+    private static final int DLG_CONFIRM_KILL_STORAGE_USERS = 1;
+    private static final int DLG_ERROR_SHARING = 2;
+    static final boolean localLOGV = false;
 
     /** Used to detect when the USB cable is unplugged, so we can call finish() */
     private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
@@ -82,7 +88,6 @@
             if (mStorageManager == null) {
                 Log.w(TAG, "Failed to get StorageManager");
             }
-            mStorageManager.registerListener(mStorageListener);
         }
 
         setTitle(getString(com.android.internal.R.string.usb_storage_activity_title));
@@ -94,28 +99,9 @@
         mMessage = (TextView) findViewById(com.android.internal.R.id.message);
 
         mMountButton = (Button) findViewById(com.android.internal.R.id.mount_button);
-        mMountButton.setOnClickListener(
-            new View.OnClickListener() { 
-                 public void onClick(View v) {
-                     int rc = mStorageManager.enableUsbMassStorage();
-                     if (rc != StorageResultCode.OperationSucceeded) {
-                         Log.e(TAG, String.format("UMS enable failed (%d)", rc));
-                         showSharingError();
-                     }
-                 }
-            });
-
+        mMountButton.setOnClickListener(this);
         mUnmountButton = (Button) findViewById(com.android.internal.R.id.unmount_button);
-        mUnmountButton.setOnClickListener(
-            new View.OnClickListener() { 
-                 public void onClick(View v) {
-                     int rc = mStorageManager.disableUsbMassStorage();
-                     if (rc != StorageResultCode.OperationSucceeded) {
-                         Log.e(TAG, String.format("UMS disable failed (%d)", rc));
-                         showStoppingError();
-                     }
-                 }
-            });
+        mUnmountButton.setOnClickListener(this);
     }
 
     private void switchDisplay(boolean usbStorageInUse) {
@@ -138,6 +124,7 @@
     protected void onResume() {
         super.onResume();
 
+        mStorageManager.registerListener(mStorageListener);
         registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         try {
             switchDisplay(mStorageManager.isUsbMassStorageEnabled());
@@ -151,6 +138,9 @@
         super.onPause();
         
         unregisterReceiver(mBatteryReceiver);
+        if (mStorageManager == null && mStorageListener != null) {
+            mStorageManager.unregisterListener(mStorageListener);
+        }
     }
 
     private void handleBatteryChanged(Intent intent) {
@@ -160,15 +150,81 @@
             finish();
         }
     }
-    
-    private void showSharingError() {
-        Toast.makeText(this, com.android.internal.R.string.usb_storage_error_message,
-                Toast.LENGTH_LONG).show();
+
+    private IMountService getMountService() {
+        IBinder service = ServiceManager.getService("mount");
+        if (service != null) {
+            return IMountService.Stub.asInterface(service);
+        }
+        return null;
     }
-    
-    private void showStoppingError() {
-        Toast.makeText(this, com.android.internal.R.string.usb_storage_stop_error_message,
-                Toast.LENGTH_LONG).show();
+
+    @Override
+    public Dialog onCreateDialog(int id, Bundle args) {
+        switch (id) {
+        case DLG_CONFIRM_KILL_STORAGE_USERS:
+            return new AlertDialog.Builder(this)
+                    .setTitle(R.string.dlg_confirm_kill_storage_users_title)
+                    .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            mStorageManager.enableUsbMassStorage();
+                        }})
+                    .setNegativeButton(R.string.cancel, null)
+                    .setMessage(R.string.dlg_confirm_kill_storage_users_text)
+                    .setOnCancelListener(this)
+                    .create();
+        case DLG_ERROR_SHARING:
+            return new AlertDialog.Builder(this)
+                    .setTitle(R.string.dlg_error_title)
+                    .setNeutralButton(R.string.dlg_ok, null)
+                    .setMessage(R.string.usb_storage_error_message)
+                    .setOnCancelListener(this)
+                    .create();
+        }
+        return null;
+    }
+
+    private void showDialogInner(int id) {
+        removeDialog(id);
+        showDialog(id);
+    }
+
+    private void checkStorageUsers() {
+        IMountService ims = getMountService();
+        if (ims == null) {
+            // Display error dialog
+            showDialogInner(DLG_ERROR_SHARING);
+        }
+        String path = Environment.getExternalStorageDirectory().getPath();
+        int stUsers[] = null;
+        try {
+            if (localLOGV) Log.i(TAG, "Checking getStorageUsers");
+            stUsers = ims.getStorageUsers(path);
+        } catch (RemoteException e) {
+            showDialogInner(DLG_ERROR_SHARING);
+        }
+        if (stUsers != null && stUsers.length > 0) {
+            // Display dialog to user
+            showDialogInner(DLG_CONFIRM_KILL_STORAGE_USERS);
+        } else {
+            if (localLOGV) Log.i(TAG, "Enabling UMS");
+            mStorageManager.enableUsbMassStorage();
+        }
+    }
+
+    public void onClick(View v) {
+        Log.i(TAG, "Clicked button");
+        if (v == mMountButton) {
+           // Check for list of storage users and display dialog if needed.
+            checkStorageUsers();
+        } else if (v == mUnmountButton) {
+            if (localLOGV) Log.i(TAG, "Disabling UMS");
+            mStorageManager.disableUsbMassStorage();
+        }
+    }
+
+    public void onCancel(DialogInterface dialog) {
+        finish();
     }
 
 }